Next major version 2 with PHP 8.1, Symfony 6, Tabler UI, 2FA ... (#2902)
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
{% macro about(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'about', view) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
@@ -1,66 +0,0 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "about/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'about.title'|trans({}, 'about') }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.about('index') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
|
||||
<div class="box box-{{ admin_lte_context.widget.type }}">
|
||||
<div class="box-body">
|
||||
<img src="{{ asset('apple-touch-icon.png') }}" class="profile-user-img img-responsive img-circle" alt="Kimai logo" style="border: 0;">
|
||||
<h3 class="profile-username text-center">{{ constant('App\\Constants::SOFTWARE') }}</h3>
|
||||
|
||||
<p class="text-muted text-center">
|
||||
{{ constant('App\\Constants::VERSION') }}
|
||||
{{ constant('App\\Constants::STATUS') }}
|
||||
({{ constant('App\\Constants::NAME') }})
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<ul class="nav nav-stacked">
|
||||
<li>
|
||||
<a href="{{ ''|docu_link }}" target="_blank">
|
||||
<i class="menu-icon fas fa-question bg-navy"></i>
|
||||
{{ 'help'|trans({}, 'about') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ constant('App\\Constants::GITHUB') }}" target="_blank">
|
||||
<i class="menu-icon fab fa-github bg-gray"></i>
|
||||
{{ 'support'|trans({}, 'about') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ constant('App\\Constants::HOMEPAGE') }}" target="_blank">
|
||||
<i class="menu-icon fa fa-home bg-green"></i>
|
||||
{{ 'website'|trans({}, 'about') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ constant('App\\Constants::HOMEPAGE') }}/donate/" target="_blank">
|
||||
<i class="menu-icon fas fa-hand-holding-heart bg-blue"></i>
|
||||
{{ 'donate'|trans({}, 'about') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<p class="text-center">
|
||||
Made with ♥ by <a href="https://www.kevinpapst.de/">Kevin Papst</a> and <a href="{{ constant('App\\Constants::GITHUB') }}graphs/contributors" target="_blank">great contributors</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block about_box %}{% endblock %}
|
||||
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
{% block about %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,40 +1,135 @@
|
||||
{% extends 'about/layout.html.twig' %}
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import '@theme/components/badge.html.twig' as badge %}
|
||||
|
||||
{% block about %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% block box_title %}{{ 'published_under'|trans({'%kimai%': constant('App\\Constants::SOFTWARE')}, 'about') }}{% endblock %}
|
||||
{% block box_body %}
|
||||
<pre>
|
||||
{{ license }}
|
||||
</pre>
|
||||
{% endblock %}
|
||||
{% block box_footer %}
|
||||
<a class="btn btn-default" href="https://choosealicense.com/licenses/mit/" target="_blank">choosealicense.com</a>
|
||||
<a class="btn btn-default" href="https://en.wikipedia.org/wiki/MIT_License" target="_blank">wikipedia.org</a>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% block main %}
|
||||
<div class="row row-cards mb-3">
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-body card_intro p-4 text-center">
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% block box_title %}{{ 'special_thanks'|trans({}, 'about') }}{% endblock %}
|
||||
{% block box_body %}
|
||||
<p>{{ 'library_authors'|trans({}, 'about') }}</p>
|
||||
<ul>
|
||||
<li>AdminLTE: <a href="https://adminlte.io" target="_blank">https://adminlte.io</a></li>
|
||||
<li>Bootstrap: <a href="https://getbootstrap.com" target="_blank">https://getbootstrap.com</a></li>
|
||||
<li>chart.js: <a href="https://www.chartjs.org" target="_blank">https://www.chartjs.org</a></li>
|
||||
<li>DateRangePicker: <a href="http://www.daterangepicker.com/" target="_blank">http://www.daterangepicker.com/</a></li>
|
||||
<li>Doctrine: <a href="https://www.doctrine-project.org" target="_blank">https://www.doctrine-project.org</a></li>
|
||||
<li>FontAwesome: <a href="https://fontawesome.com" target="_blank">https://fontawesome.com</a></li>
|
||||
<li>FullCalendar: <a href="https://fullcalendar.io" target="_blank">https://fullcalendar.io</a></li>
|
||||
<li>jQuery: <a href="https://jquery.com" target="_blank">https://jquery.com</a></li>
|
||||
<li>jQuery UI: <a href="https://jqueryui.com" target="_blank">http://jqueryui.com</a></li>
|
||||
<li>MomentJS: <a href="https://momentjs.com" target="_blank">https://momentjs.com</a></li>
|
||||
<li>PHPOffice: <a href="https://github.com/PHPOffice" target="_blank">https://github.com/PHPOffice</a></li>
|
||||
<li>Select2: <a href="https://select2.org/" target="_blank">https://select2.org/</a></li>
|
||||
<li>SweetAlert 2: <a href="https://sweetalert2.github.io" target="_blank">https://sweetalert2.github.io</a></li>
|
||||
<li>Symfony: <a href="https://symfony.com" target="_blank">https://symfony.com</a></li>
|
||||
<li>… </li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
<img src="{{ asset('apple-touch-icon.png') }}" class="avatar avatar-2xl mb-3 avatar-rounded" alt="{{ constant('App\\Constants::SOFTWARE') }}">
|
||||
|
||||
<h3 class="m-0 mb-1">
|
||||
<a href="{{ constant('App\\Constants::HOMEPAGE') }}" target="_blank">{{ constant('App\\Constants::SOFTWARE') }}</a>
|
||||
</h3>
|
||||
<div class="text-muted">
|
||||
{{ constant('App\\Constants::VERSION') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<a href="{{ ''|docu_link }}" target="_blank" class="card-btn">
|
||||
<i class="me-2 text-muted {{ 'help'|icon(false) }}"></i>
|
||||
{{ 'help'|trans({}, 'about') }}
|
||||
</a>
|
||||
<a href="{{ constant('App\\Constants::GITHUB') }}discussions" target="_blank" class="card-btn">
|
||||
<i class="me-2 text-muted {{ 'fab fa-github'|icon(false) }}"></i>
|
||||
{{ 'support'|trans({}, 'about') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<a href="{{ constant('App\\Constants::HOMEPAGE') }}" target="_blank" class="card-btn">
|
||||
<i class="me-2 text-muted {{ 'home'|icon(false) }}"></i>
|
||||
{{ 'website'|trans({}, 'about') }}
|
||||
</a>
|
||||
<a href="{{ constant('App\\Constants::HOMEPAGE') }}/donate/" target="_blank" class="card-btn">
|
||||
<i class="me-2 text-muted {{ 'fas fa-hand-holding-heart'|icon(false) }}"></i>
|
||||
{{ 'donate'|trans({}, 'about') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
Made with ♥ by <a href="https://www.kevinpapst.de/">Kevin Papst</a> and <a href="{{ constant('App\\Constants::GITHUB') }}graphs/contributors" target="_blank">great contributors</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% block box_title %}Credits{% endblock %}
|
||||
{% block box_body_class %}credits_card{% endblock %}
|
||||
{% block box_body %}
|
||||
<p>
|
||||
The following amazing software libraries were used when creating Kimai. It wouldn't be possible without them.
|
||||
My biggest thanks go out to the authors 👍
|
||||
</p>
|
||||
<ul>
|
||||
<li>Bootstrap: <a href="https://getbootstrap.com" target="_blank">https://getbootstrap.com</a></li>
|
||||
<li>Chart.js: <a href="https://www.chartjs.org" target="_blank">https://www.chartjs.org</a></li>
|
||||
<li>Doctrine: <a href="https://www.doctrine-project.org" target="_blank">https://www.doctrine-project.org</a></li>
|
||||
<li>FontAwesome: <a href="https://fontawesome.com" target="_blank">https://fontawesome.com</a></li>
|
||||
<li>FullCalendar: <a href="https://fullcalendar.io" target="_blank">https://fullcalendar.io</a></li>
|
||||
<li>gridstack.js: <a href="https://gridstackjs.com" target="_blank">https://gridstackjs.com</a></li>
|
||||
<li>Litepicker: <a href="https://litepicker.com/" target="_blank">https://litepicker.com/</a></li>
|
||||
<li>Luxon: <a href="https://moment.github.io/luxon/" target="_blank">https://moment.github.io/luxon/</a></li>
|
||||
<li>MPDF <a href="https://mpdf.github.io" target="_blank">https://mpdf.github.io</a></li>
|
||||
<li>PHPOffice: <a href="https://github.com/PHPOffice" target="_blank">https://github.com/PHPOffice</a></li>
|
||||
<li>Symfony: <a href="https://symfony.com" target="_blank">https://symfony.com</a></li>
|
||||
<li>Tabler: <a href="https://tabler.io" target="_blank">https://tabler.io</a></li>
|
||||
<li>Tom Select: <a href="https://tom-select.js.org/" target="_blank">https://tom-select.js.org/</a></li>
|
||||
<li>Twig: <a href="https://twig.symfony.com" target="_blank">https://twig.symfony.com</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% block box_footer %}
|
||||
Apologies to all projects that I forgot to mention. Please feel free and send a PR to fix that!
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cards mb-3">
|
||||
<div class="col-lg-8">
|
||||
<div class="card card-lg">
|
||||
<div class="card-body card_license">
|
||||
<div class="markdown">
|
||||
<h3>License</h3>
|
||||
<div style="white-space: pre">{{ license|nl2br|replace({'<br />': ''})|raw }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-body card_details">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="me-3">
|
||||
<i class="fa-2x fas fa-balance-scale"></i>
|
||||
</div>
|
||||
<div>
|
||||
<small class="text-muted">{{ constant('\\App\\Constants::SOFTWARE') }} is licensed under the</small>
|
||||
<h3 class="lh-1">GNU Affero General Public License v3.0</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted mb-3">
|
||||
Permissions of this strongest copyleft license are conditioned on making available complete source code of licensed works and modifications,
|
||||
which include larger works using a licensed work, under the same license.
|
||||
Copyright and license notices must be preserved. Contributors provide an express grant of patent rights.
|
||||
When a modified version is used to provide a service over a network, the complete source code of the modified version must be made available.
|
||||
</div>
|
||||
<h4>Permissions</h4>
|
||||
<ul class="list-unstyled space-y-1">
|
||||
<li><i class="{{ 'success'|icon(false) }} text-green"></i> Commercial use</li>
|
||||
<li><i class="{{ 'success'|icon(false) }} text-green"></i> Modification</li>
|
||||
<li><i class="{{ 'success'|icon(false) }} text-green"></i> Distribution</li>
|
||||
<li><i class="{{ 'success'|icon(false) }} text-green"></i> Patent use</li>
|
||||
<li><i class="{{ 'success'|icon(false) }} text-green"></i> Private use</li>
|
||||
</ul>
|
||||
<h4>Limitations</h4>
|
||||
<ul class="list-unstyled space-y-1">
|
||||
<li><i class="{{ 'failure'|icon(false) }} text-red"></i> Liability</li>
|
||||
<li><i class="{{ 'failure'|icon(false) }} text-red"></i> Warranty</li>
|
||||
</ul>
|
||||
<h4>Conditions</h4>
|
||||
<ul class="list-unstyled space-y-1">
|
||||
<li><i class="{{ 'details'|icon(false) }} text-blue"></i> Disclose source</li>
|
||||
<li><i class="{{ 'details'|icon(false) }} text-blue"></i> License and copyright notice</li>
|
||||
<li><i class="{{ 'details'|icon(false) }} text-blue"></i> Network use is distribution</li>
|
||||
<li><i class="{{ 'details'|icon(false) }} text-blue"></i> Same license</li>
|
||||
<li><i class="{{ 'details'|icon(false) }} text-blue"></i> State changes</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
This is not legal advice. Learn more about this licenses at
|
||||
<a href="https://choosealicense.com/licenses/agpl-3.0/" target="_blank">choosealicense.com</a> and
|
||||
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank">GNU</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
{% macro activities(view, query) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'activities', view, {'query': query}) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro activity(activity, view, isTable) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'activity', view, {'activity': activity}) %}
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'activities'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'delete') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% set inUse = (stats.counter > 0) %}
|
||||
|
||||
{% set params = {
|
||||
'%records%': stats.counter,
|
||||
'%duration%': stats.duration|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,
|
||||
'used': (stats.counter > 0),
|
||||
'back': path('admin_activity')
|
||||
}) }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'activities'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'activity_details') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% set can_edit = is_granted('edit', activity) %}
|
||||
@@ -12,7 +8,7 @@
|
||||
{% set currency = activity.project.customer.currency %}
|
||||
{% endif %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "customer/actions.html.twig" as customerActions %}
|
||||
{% import "project/actions.html.twig" as projectActions %}
|
||||
@@ -22,20 +18,20 @@
|
||||
{% endblock %}
|
||||
{% block box_tools %}
|
||||
{% if can_edit %}
|
||||
<a class="modal-ajax-form open-edit btn btn-default btn-sm" data-href="{{ path('admin_activity_edit', {'id': activity.id}) }}" data-toggle="tooltip" data-placement="top" title="{{ 'action.edit'|trans }}"><i class="{{ 'edit'|icon }}"></i></a>
|
||||
{{ widgets.card_tool_button('edit', {'class': 'modal-ajax-form open-edit', 'title': 'action.edit', 'url': path('admin_activity_edit', {'id': activity.id})}) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if activity.comment is not empty %}
|
||||
<div class="comment">
|
||||
<div class="comment p-3">
|
||||
{{ activity.comment|comment2html(true) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<table class="table table-hover dataTable">
|
||||
{% if not activity.visible %}
|
||||
<tr class="{{ widgets.class_activity_row(activity, now) }}">
|
||||
<th>{{ 'label.visible'|trans }}</th>
|
||||
<th>{{ 'visible'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ widgets.label_boolean(activity.visible) }}
|
||||
</td>
|
||||
@@ -43,7 +39,7 @@
|
||||
{% endif %}
|
||||
{% if not activity.billable %}
|
||||
<tr>
|
||||
<th>{{ 'label.billable'|trans }}</th>
|
||||
<th>{{ 'billable'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ widgets.label_boolean(activity.billable) }}
|
||||
</td>
|
||||
@@ -51,7 +47,7 @@
|
||||
{% endif %}
|
||||
{% if not activity.global %}
|
||||
<tr {{ widgets.customer_row_attr(activity.project.customer, now) }}>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th>{{ 'customer'|trans }}</th>
|
||||
<td>
|
||||
{{ widgets.label_customer(activity.project.customer) }}
|
||||
</td>
|
||||
@@ -63,7 +59,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr {{ widgets.project_row_attr(activity.project, now) }}>
|
||||
<th>{{ 'label.project'|trans }}</th>
|
||||
<th>{{ 'project'|trans }}</th>
|
||||
<td>
|
||||
{{ widgets.label_project(activity.project) }}
|
||||
</td>
|
||||
@@ -85,8 +81,12 @@
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
{% if can_edit %}
|
||||
{{ include('embeds/rates-table.html.twig', {'id': 'activity_rates_box', 'entity': activity, 'create_url': path('admin_activity_rate_add', {'id': activity.id}), 'delete_route': 'delete_activity_rate', 'currency': currency, 'edit_route': 'admin_activity_rate_edit'}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if stats is not null %}
|
||||
{% set currency = kimai_config.getCustomerDefaultCurrency() %}
|
||||
{% set currency = kimai_config.customerDefaultCurrency %}
|
||||
{% if activity.project is not null %}
|
||||
{% set currency = activity.project.customer.currency %}
|
||||
{% endif %}
|
||||
@@ -97,10 +97,6 @@
|
||||
{{ render(controller(controller, {'activity': activity, 'page': 1})) }}
|
||||
{% endfor %}
|
||||
|
||||
{% if can_edit %}
|
||||
{{ include('embeds/rates-table.html.twig', {'id': 'activity_rates_box', 'entity': activity, 'create_url': path('admin_activity_rate_add', {'id': activity.id}), 'delete_route': 'delete_activity_rate', 'currency': currency}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if teams is not null %}
|
||||
{% set options = {'teams': teams, 'team': team} %}
|
||||
{% if is_granted('permissions', activity) %}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'activities'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'edit') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% 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': (activity.id is null ? 'create-activity'|trans({}, 'actions') : 'edit'|trans({}, 'actions')),
|
||||
'form': form,
|
||||
@@ -14,10 +10,10 @@
|
||||
{% embed formEditTemplate with formOptions %}
|
||||
{% block form_body %}
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<div class="col-md-9">
|
||||
{{ form_row(form.name) }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.color) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,8 +41,12 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.invoiceText is defined %}
|
||||
{{ form_row(form.invoiceText) }}
|
||||
{% if form.teams is defined %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{{ form_row(form.teams) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@@ -57,11 +57,19 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if form.metaFields is defined and form.metaFields is not empty %}
|
||||
{% for meta in form.metaFields|sort((a, b) => a.vars.data.order <=> b.vars.data.order) %}
|
||||
{{ form_row(meta) }}
|
||||
{% endfor %}
|
||||
{{ form_row(form.metaFields) }}
|
||||
{% endif %}
|
||||
{{ form_widget(form) }}
|
||||
{% embed '@theme/embeds/card.html.twig' with {collapsed: true} %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'invoices'|trans }}{% endblock %}
|
||||
{% block box_body_class %}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if form.invoiceText is defined %}
|
||||
{{ form_row(form.invoiceText) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{{ form_rest(form) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,96 +1,46 @@
|
||||
{% 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 "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'name': {'class': 'alwaysVisible'},
|
||||
'project': {'class': 'hidden-xs'},
|
||||
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
|
||||
} %}
|
||||
{% for field in metaColumns %}
|
||||
{% set columns = columns|merge({
|
||||
('mf_' ~ field.name): {'title': field.label|trans, 'class': 'hidden-xs hidden-sm mf_' ~ field.name, 'orderBy': false}
|
||||
}) %}
|
||||
{% endfor %}
|
||||
{% if is_granted('budget_money', 'activity') %}
|
||||
{% set columns = columns|merge({
|
||||
'budget': {'class': 'hidden-xs hidden-sm hidden text-right w-min', 'title': 'label.budget'|trans},
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget_time', 'activity') %}
|
||||
{% set columns = columns|merge({
|
||||
'timeBudget': {'class': 'hidden-xs hidden-sm hidden text-right w-min', 'title': 'label.timeBudget'|trans},
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% set columns = columns|merge({
|
||||
'team': {'class': 'text-center w-min', 'orderBy': false},
|
||||
'visible': {'class': 'text-center hidden w-min'},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
}) %}
|
||||
{% block datatable_row_attr %} {{ widgets.activity_row_attr(entry, now) }}{% endblock %}
|
||||
|
||||
{% set tableName = 'activity_admin' %}
|
||||
|
||||
{% block page_title %}{{ 'activities'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activities('index', query) }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.nothing_found('kimai.activityUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.activityUpdate kimai.activityDelete kimai.activityTeamUpdate'}) }}
|
||||
|
||||
{% for entry in entries %}
|
||||
<tr {{ widgets.activity_row_attr(entry, now) }}>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
|
||||
{{ widgets.label_name(entry.name, entry.color) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">
|
||||
{# only none-global activities have a project and customer assigned #}
|
||||
{% if entry.project %}
|
||||
{{ 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|comment1line }}</td>
|
||||
{% for field in metaColumns %}
|
||||
<td class="{{ 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, 'budget') }}">
|
||||
{% if entry.hasBudget() and is_granted('budget', entry) %}
|
||||
{{ entry.budget|money((entry.project is null ? defaultCurrency : entry.project.customer.currency)) }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'timeBudget') }}">
|
||||
{% if entry.hasTimeBudget() and is_granted('time', entry) %}
|
||||
{{ entry.timeBudget|duration }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
<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, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">{{ actions.activity(entry, 'index') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{{ tables.data_table_footer(entries, 'admin_activity_paginated') }}
|
||||
{% block datatable_column_value %}
|
||||
{% if column == 'name' %}
|
||||
{{ widgets.label_name(entry.name, entry.color|colorize(entry.name)) }}
|
||||
{% elseif column == 'project' %}
|
||||
{# only none-global activities have a project and customer assigned #}
|
||||
{% if entry.project %}
|
||||
{{ widgets.label_project(entry.project) }}
|
||||
{% if entry.project.customer %}
|
||||
<br>
|
||||
<small>
|
||||
{{ widgets.label_customer(entry.project.customer) }}
|
||||
</small>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% elseif column == 'comment' %}
|
||||
{{ entry.comment|comment1line }}
|
||||
{% elseif column == 'budget' %}
|
||||
{% if entry.hasBudget() and is_granted('budget', entry) %}
|
||||
{{ entry.budget|money((entry.project is null ? defaultCurrency : entry.project.customer.currency)) }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{% elseif column == 'timeBudget' %}
|
||||
{% if entry.hasTimeBudget() and is_granted('time', entry) %}
|
||||
{{ entry.timeBudget|duration }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{% elseif column == 'billable' %}
|
||||
{{ widgets.label_boolean(entry.billable) }}
|
||||
{% elseif column == 'team' %}
|
||||
{{ widgets.badge_team_access(entry.teams) }}
|
||||
{% elseif column == 'visible' %}
|
||||
{{ widgets.label_visible(entry.visible) }}
|
||||
{% elseif column == 'actions' %}
|
||||
{{ actions.activity(entry, 'index') }}
|
||||
{% elseif column starts with 'mf_' %}
|
||||
{{ widgets.meta_field_value(entry, data) }}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'activities'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'permissions') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block main %}
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': ('permissions'|trans({}, 'actions')) ~ ': ' ~ activity.name,
|
||||
'form': form,
|
||||
'back': path('activity_details', {'id': activity.id})
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'activities'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.activity(activity, 'rates') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block main %}
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': activity.name,
|
||||
'form': form,
|
||||
'back': path('admin_activity')
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
{% extends '@AdminLTE/layout/default-layout.html.twig' %}
|
||||
{% extends '@theme/layout/default-layout.html.twig' %}
|
||||
|
||||
{% block body_start %}
|
||||
data-title="{{- get_title()|e('html_attr') -}}"
|
||||
{% endblock %}
|
||||
|
||||
{% block page_title %}{{- get_title() -}}{% endblock %}
|
||||
{%- endblock -%}
|
||||
|
||||
{% block after_body_start %}
|
||||
{% embed 'embeds/modal.html.twig' %}
|
||||
{% embed '@theme/embeds/modal.html.twig' %}
|
||||
{% block modal_id %}remote_form_modal{% endblock %}
|
||||
{% block modal_title %}{% endblock %}
|
||||
{% block modal_body %}{% endblock %}
|
||||
{% block modal_footer %}{% endblock %}
|
||||
{% endembed %}
|
||||
{% block page_search %}{% endblock %}
|
||||
<div id="toast-container" class="toast-container position-fixed top-0 start-50 translate-middle-x p-3" style="z-index: 11"></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content_class %}{{ parent() }} {% block page_class %}{% endblock %}{% endblock %}
|
||||
|
||||
{% block page_content_start %}
|
||||
{% if app.session and app.session.started and app.session.flashbag.peekAll|length > 0 %}
|
||||
{% set domain = 'flashmessages' %}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function(options) {
|
||||
var ALERT = options.detail.kimai.getPlugin('alert');
|
||||
const ALERT = options.detail.kimai.getPlugin('alert');
|
||||
{% for type, messages in app.session.flashbag.all %}
|
||||
{% for message in messages %}
|
||||
{% if type == 'error' %}
|
||||
@@ -43,7 +44,7 @@
|
||||
{% block page_content_before %}
|
||||
{% set event = trigger(constant('App\\Event\\ThemeEvent::CONTENT_BEFORE')) %}
|
||||
{{ event.content|raw }}
|
||||
<div class="no-print">
|
||||
<div class="d-print-none hidden-no-space">
|
||||
{% block main_before %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -52,6 +53,14 @@
|
||||
{% block main_after %}{% endblock %}
|
||||
{% set event = trigger(constant('App\\Event\\ThemeEvent::CONTENT_AFTER')) %}
|
||||
{{ event.content|raw }}
|
||||
{% if page_setup is defined and page_setup.help is not null %}
|
||||
<div class="float-help">
|
||||
<a href="{% if '://' in page_setup.help %}{{ page_setup.help }}{% else %}{{ page_setup.help|docu_link }}{% endif %}" target="_blank" accesskey="h">
|
||||
<i class="fas fa-question"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mb-4"></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
@@ -66,213 +75,31 @@
|
||||
{{- get_title() -}}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_subtitle %}{% endblock %}
|
||||
|
||||
{% block logo_mini %}
|
||||
{% set mini = config('theme.branding.mini') %}
|
||||
{% if mini is not empty %}
|
||||
{{ mini|raw }}
|
||||
{% else %}
|
||||
<b>K</b>TT
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block logo_large %}
|
||||
{% set company = config('theme.branding.company') %}
|
||||
{% if company is not empty %}
|
||||
{{ company|raw }}
|
||||
{% else %}
|
||||
<b>Kimai</b> - Time Tracking
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block page_actions_class %}col-auto ms-auto d-print-none{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
<!-- Page rendered on {{ 'now'|date_full }} -->
|
||||
{% endblock %}
|
||||
|
||||
{#
|
||||
{% block navbar_toggle %}
|
||||
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
|
||||
<span class="sr-only">{{ 'Toggle navigation'|trans({}, 'AdminLTEBundle') }}</span>
|
||||
</a>
|
||||
{% if app.user is not null and is_granted('IS_AUTHENTICATED_REMEMBERED') and is_granted('view_own_timesheet') %}
|
||||
<div class="navbar-custom-menu" style="float:left">
|
||||
<ul class="nav navbar-nav">
|
||||
{% if app.user.preferenceValue('login.initial_view') == 'calendar' %}
|
||||
<li class="pull-left visible-xs-inline-block">
|
||||
<a href="{{ path('calendar') }}" class="ddt-small">
|
||||
<i class="{{ 'calendar'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="pull-left visible-xs-inline-block">
|
||||
<a href="{{ path('timesheet') }}" class="ddt-small">
|
||||
<i class="{{ 'timesheet'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% if 'dashboard' in app.request.attributes.get('_route') %}
|
||||
{{ parent() }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
#}
|
||||
|
||||
{% block navbar_start %}
|
||||
{% if app.user is not null and is_granted('IS_AUTHENTICATED_REMEMBERED') %}
|
||||
{% block navbar_extensions %}{% endblock %}
|
||||
{% if is_granted('create_own_timesheet') %}
|
||||
{% set active_timesheets = active_timesheets(app.user) %}
|
||||
{% set hasActiveRecords = active_timesheets is not empty %}
|
||||
{% if not hasActiveRecords %}
|
||||
{# fake entry, because at least one html template node is needed #}
|
||||
{% set active_timesheets = [{'id': '000', 'begin': null, 'activity': {'name': ''}, 'project': {'name': '', 'customer': {'name': ''}}}] %}
|
||||
{% endif %}
|
||||
{% set active_limit = kimai_config.timesheetActiveEntriesHardLimit %}
|
||||
<li class="messages-menu {% if active_limit > 1 and hasActiveRecords %}dropdown{% endif %}" data-api="{{ path('active_timesheet') }}" data-href="{{ path('stop_timesheet', {'id' : '000'}) }}" data-icon="{{ 'stop-small'|icon }}" style="{% if not hasActiveRecords %}display:none{% endif %}">
|
||||
{% if active_limit == 1 %}
|
||||
{% set entry = active_timesheets[0] %}
|
||||
<div class="ddt-small ticktac-single ticktac-running">
|
||||
<div class="ticktac-stop">
|
||||
<a accesskey="s" data-replacer="url" class="api-link" href="{{ path('stop_timesheet', {'id' : entry.id}) }}" data-event="kimai.timesheetStop kimai.timesheetUpdate" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">
|
||||
<i class="{{ 'stop-small'|icon }} fa-2x"></i><span data-replacer="duration" data-title="true" data-since="{{ entry.begin is null ? '' : entry.begin|date_format(constant('DATE_ISO8601')) }}">{{ entry|duration }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="#" class="dropdown-toggle ddt-small ticktac" data-toggle="dropdown">
|
||||
<i class="{{ 'start'|icon }} fa-2x"></i>
|
||||
<span data-warning="{{ active_limit }}" class="label label-warning">{% if hasActiveRecords %}{{ active_timesheets|length }}{% endif %}</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="header">
|
||||
{{ 'active.entries'|trans }}
|
||||
</li>
|
||||
<li>
|
||||
<ul class="menu">
|
||||
{% for entry in active_timesheets %}
|
||||
<li data-template="active-record">
|
||||
<a accesskey="s" data-replacer="url" href="{{ path('stop_timesheet', {'id' : entry.id}) }}" class="api-link" data-event="kimai.timesheetStop kimai.timesheetUpdate" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">
|
||||
<div class="pull-left">
|
||||
<i class="{{ 'stop-small'|icon }} fa-2x"></i>
|
||||
</div>
|
||||
<h4>
|
||||
<span data-replacer="activity">{{ entry.activity.name }}</span>
|
||||
<small>
|
||||
<span data-replacer="duration" data-title="true" data-since="{{ entry.begin is null ? '' : entry.begin|date_format(constant('DATE_ISO8601')) }}">{{ entry|duration }}</span>
|
||||
</small>
|
||||
</h4>
|
||||
<p><span data-replacer="project">{{ entry.project.name }}</span> (<span data-replacer="customer">{{ entry.project.customer.name }}</span>)</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="footer"><a href="{{ path('timesheet_create') }}" class="modal-ajax-form">{{ 'timesheet.start'|trans }}</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="messages-menu-empty" style="{% if hasActiveRecords %}display:none{% endif %}">
|
||||
{% if active_limit == 1 %}
|
||||
<div class="ddt-small ticktac-single ticktac-stopped">
|
||||
<a accesskey="n" href="{{ path('timesheet_create') }}" class="modal-ajax-form ticktac-start">
|
||||
<i class="{{ 'start'|icon }} fa-2x"></i>
|
||||
<span>{{ 0|duration }}</span>
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<a accesskey="n" href="{{ path('timesheet_create') }}" class="ddt-small ticktac-start modal-ajax-form">
|
||||
<i class="{{ 'start'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% include 'partials/ticktack.html.twig' %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_own_timesheet') %}
|
||||
{% include 'partials/recent-activities.html.twig' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{# these blocks and the hook-in logic by the AdminTheme could be re-used by Kimai or an extension at some point #}
|
||||
{#
|
||||
{% block navbar_messages %}{% endblock %}
|
||||
{% block navbar_notifications %}{% endblock %}
|
||||
{% block navbar_tasks %}{% endblock %}
|
||||
{% block navbar_end %}{% endblock %}
|
||||
#}
|
||||
|
||||
{# deactivated blocks, as Kimai does not ship the sidebar for UX reasons #}
|
||||
{% block sidebar_user %}{% endblock %}
|
||||
{% block sidebar_search %}{% endblock %}
|
||||
|
||||
{% block navbar_user %}
|
||||
{% if app.user is not null and is_granted('view_own_timesheet') %}
|
||||
{% include 'navbar/recent-activities.html.twig' %}
|
||||
{% endif %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
<li class="dropdown user-menu">
|
||||
<a href="#" class="dropdown-toggle ddt-small" data-toggle="dropdown">
|
||||
{{ widgets.user_avatar(app.user, false) }}
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
{% if app.user is not null %}
|
||||
{% if is_granted('view', app.user) %}
|
||||
<li>
|
||||
<a href="{{ path('user_profile', {'username' : app.user.username}) }}">
|
||||
<h4 class="control-sidebar-subheading">
|
||||
<i class="{{ 'user'|icon }}"></i>
|
||||
{{ 'my.profile'|trans }}
|
||||
</h4>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('edit', app.user) %}
|
||||
<li>
|
||||
<a href="{{ path('user_profile_edit', {'username' : app.user.username}) }}">
|
||||
<h4 class="control-sidebar-subheading">
|
||||
<i class="{{ 'profile'|icon }}"></i>
|
||||
{{ 'action.edit'|trans }}
|
||||
</h4>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('preferences', app.user) %}
|
||||
<li>
|
||||
<a href="{{ path('user_profile_preferences', {'username' : app.user.username}) }}">
|
||||
<h4 class="control-sidebar-subheading">
|
||||
<i class="{{ 'settings'|icon }}"></i>
|
||||
{{ 'profile.preferences'|trans }}
|
||||
</h4>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{{ path('fos_user_security_logout') }}">
|
||||
<h4 class="control-sidebar-subheading">
|
||||
<i class="{{ 'logout'|icon }}"></i>
|
||||
{{ 'menu.logout'|trans }}
|
||||
</h4>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if config('theme.show_about') %}
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<a href="{{ path('about') }}">
|
||||
<h4 class="control-sidebar-subheading">
|
||||
<i class="{{ 'about'|icon }}"></i>
|
||||
{{ 'about.title'|trans({}, 'about') }}
|
||||
</h4>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{% block page_actions %}{% endblock %}
|
||||
{% endblock %}
|
||||
{# deactivated blocks from the original theme #}
|
||||
{% block page_pretitle %}{% endblock %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{# we do not call parent() as we use a custom built for the frontend assets and don't want the default <stylesheet> #}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('app') }}
|
||||
{% set event = trigger(constant('App\\Event\\ThemeEvent::STYLESHEET')) %}
|
||||
{{ event.content|raw }}
|
||||
@@ -293,3 +120,67 @@
|
||||
{% set event = trigger(constant('App\\Event\\ThemeEvent::JAVASCRIPT')) %}
|
||||
{{ event.content|raw }}
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar_brand_class %} d-none d-lg-inline-flex{% endblock %}
|
||||
|
||||
{% block app_logo %}
|
||||
{% set mylogo %}{{ parent() }}{% endset %}
|
||||
{# only for mobile view #}
|
||||
<div class="d-flex justify-content-start flex-fill d-lg-none">
|
||||
<h2 class="page-title d-lg-none ps-2 text-white">
|
||||
{{- block('page_title') -}}
|
||||
</h2>
|
||||
{{ mylogo }}
|
||||
</div>
|
||||
{{ mylogo }}
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar_search %}
|
||||
{# normal desktop view #}
|
||||
<h2 class="page-title me-2">
|
||||
{{- block('page_title') -}}
|
||||
</h2>
|
||||
{%- if block('status') is defined -%}<div class="btn-list">{{ block('status') }}</div>{%- endif -%}
|
||||
{%- if block('subtitle') is defined -%}{{ block('subtitle') }}{%- endif -%}
|
||||
{%- if block('breadcrumb') is defined and block('table_actions') is defined -%}{{ block('breadcrumb') }}{%- endif -%}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_intro %}
|
||||
{% if block('table_actions') is defined %}
|
||||
{{ block('table_actions') }}
|
||||
{% elseif block('breadcrumb') is defined %}
|
||||
<div class="text-muted mt-1">
|
||||
{{ block('breadcrumb') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_title %}
|
||||
{%- if page_setup is defined -%}
|
||||
{{ page_setup.title|trans }}
|
||||
{%- else -%}
|
||||
{{ get_title() }}
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_actions %}
|
||||
{% from "macros/widgets.html.twig" import page_actions, table_actions %}
|
||||
{%- if page_setup is defined and page_setup.actionName is not null -%}
|
||||
{% set event = actions(app.user, page_setup.actionName, page_setup.actionView, page_setup.actionPayload) %}
|
||||
{% if page_setup.tableAction() %}
|
||||
{{ table_actions(event.actions) }}
|
||||
{% else %}
|
||||
{{ page_actions(event.actions) }}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{%- if page_setup is defined -%}
|
||||
{%- if page_setup.actionName is not null or page_setup.hasSearchForm() -%}
|
||||
{{ parent() }}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{{ parent() }}
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{% macro avatar_image(user) %}
|
||||
{% if user.avatar is not empty and kimai_config.themeAllowAvatarUrls %}
|
||||
{% set avatar = asset(user.avatar, 'avatars') %}
|
||||
<span class="avatar" style="background-image:url({{ avatar }})" title="{{ user.displayName }}"> </span>
|
||||
{% else %}
|
||||
{% set color = user.color|colorize(user.displayName) %}
|
||||
<span class="avatar" style="background-color: {{ color }}; color: {{ color|font_contrast }}" title="{{ user.displayName }}">{{ user.initials }}</span>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
16
templates/bundles/TablerBundle/includes/footer.html.twig
Normal file
16
templates/bundles/TablerBundle/includes/footer.html.twig
Normal file
@@ -0,0 +1,16 @@
|
||||
{% if config('theme.show_about') %}
|
||||
<div class="row text-center align-items-center flex-row">
|
||||
<div class="col-12 col-lg-auto mt-3 mt-lg-0">
|
||||
<ul class="list-inline list-inline-dots mb-0">
|
||||
<li class="list-inline-item">
|
||||
Copyright © <a href="https://www.kevinpapst.de/" target="_blank" class="link-secondary">Kevin Papst</a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a class="link-secondary" href="{{ path('about') }}">
|
||||
{{ 'about.title'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
14
templates/bundles/TablerBundle/includes/logo.html.twig
Normal file
14
templates/bundles/TablerBundle/includes/logo.html.twig
Normal file
@@ -0,0 +1,14 @@
|
||||
<a href="{{ path('tabler_welcome'|tabler_route) }}">
|
||||
{% set logo = config('theme.branding.logo') %}
|
||||
{% set company = config('theme.branding.company') %}
|
||||
{% if company is not empty %}
|
||||
<span>{{ company }}</span>
|
||||
{% elseif logo is not empty %}
|
||||
<img src="{{ logo }}" class="navbar-brand-image" style="max-height:32px" alt="{{ company }}">
|
||||
{% elseif tabler_bundle.getLogoUrl() is not empty %}
|
||||
<img class="navbar-brand-image" alt="{{ company }}" width="110" height="32"
|
||||
src="{% if '://' in tabler_bundle.getLogoUrl() %}{{ tabler_bundle.getLogoUrl() }}{% else %}{{ asset(tabler_bundle.getLogoUrl()) }}{% endif %}">
|
||||
{% else %}
|
||||
<span>{{ get_title()|raw }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
@@ -0,0 +1 @@
|
||||
{% extends tabler_bundle.isBoxedLayout() ? '@Tabler/layout-horizontal.html.twig' : '@Tabler/layout-vertical.html.twig' %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends '@Tabler/security.html.twig' %}
|
||||
@@ -1,39 +1 @@
|
||||
{#
|
||||
This template is used to render any error different from 403, 404 and 500.
|
||||
|
||||
This is the simplest way to customize error pages in Symfony applications.
|
||||
In case you need it, you can also hook into the internal exception handling
|
||||
made by Symfony. This allows you to perform advanced tasks and even recover
|
||||
your application from some errors.
|
||||
See http://symfony.com/doc/current/cookbook/controller/error_pages.html
|
||||
#}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="{{ _charset }}" />
|
||||
<title>{{ 'http_error.title'|trans({}, 'exceptions') }}: {{ status_text }}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% set errorTitleKey = "http_error_#{status_code}.description" %}
|
||||
{% set errorTitleTrans = errorTitleKey|trans({ '%status_code%': status_code }, 'exceptions') %}
|
||||
{% if errorTitleKey == errorTitleTrans %}
|
||||
{% set errorTitleKey = 'http_error.description' %}
|
||||
{% set errorTitleTrans = errorTitleKey|trans({ '%status_code%': status_code }, 'exceptions') %}
|
||||
{% endif %}
|
||||
|
||||
{% set errorMessageKey = "http_error_#{status_code}.suggestion" %}
|
||||
{% set errorMessageTrans = errorMessageKey|trans({ '%status_code%': status_code }, 'exceptions') %}
|
||||
{% if errorMessageKey == errorMessageTrans %}
|
||||
{% set errorMessageKey = 'http_error.suggestion' %}
|
||||
{% set errorMessageTrans = errorMessageKey|trans({ '%status_code%': status_code }, 'exceptions') %}
|
||||
{% endif %}
|
||||
|
||||
<h1>{{ errorTitleTrans }}</h1>
|
||||
<h2>{{ status_code }}: {{ status_text }}</h2>
|
||||
|
||||
<div>
|
||||
{{ errorMessageTrans }}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% extends '@Tabler/error.html.twig' %}
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
{% extends 'error.html.twig' %}
|
||||
|
||||
{% block error_icon %}
|
||||
<i class="fas fa-unlock-alt text-yellow"></i>
|
||||
{% endblock %}
|
||||
{% extends '@Tabler/error.html.twig' %}
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
{% extends 'error.html.twig' %}
|
||||
|
||||
{% block error_icon %}
|
||||
<i class="far fa-frown"></i>
|
||||
{% endblock %}
|
||||
{% extends '@Tabler/error.html.twig' %}
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
{% extends 'error.html.twig' %}
|
||||
|
||||
{% block error_icon %}
|
||||
<i class="fas fa-bug text-red"></i>
|
||||
{% endblock %}
|
||||
|
||||
{% block error_class %}text-red{% endblock %}
|
||||
{% extends '@Tabler/error.html.twig' %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% block dd_timesheet %}
|
||||
{% import "calendar/widgets.html.twig" as widgets %}
|
||||
{{ widgets.dd_event(entry.title, entry.project ? entry.project.customer.name : null, entry.project ? entry.project.name : null, null) }}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "calendar/widgets.html.twig" as calendar %}
|
||||
{{ calendar.dd_event(widgets.label_color(entry.title, entry.color), entry.project ? widgets.label_customer(entry.project.customer) : null, entry.project ? widgets.label_project(entry.project) : null, null) }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block page_title %}{{ 'calendar'|trans }}{% endblock %}
|
||||
{% block page_actions %}
|
||||
{% set event = actions(app.user, 'calendar', 'index') %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
{% set hasTwoColumns = (config.dragDropAmount > 0 and dragAndDrop is not empty and (dragAndDrop|filter(s => s.entries|length > 0)|length > 0)) or form is not null %}
|
||||
{% if hasTwoColumns %}
|
||||
<div class="hidden-xs col-sm-5 col-md-4 col-lg-3 no-print">
|
||||
<div class="col-md-4 col-lg-3 d-none d-md-block">
|
||||
{% if form is not null %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% block box_body %}
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
@@ -23,18 +16,22 @@
|
||||
{% endembed %}
|
||||
{% endif %}
|
||||
{% for source in dragAndDrop|filter(s => s.entries|length > 0) %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% block box_title %}{{ source.title|trans }}{% endblock %}
|
||||
{% block box_body_class %}drag-and-drop-source{% endblock %}
|
||||
{% block box_body_class %}drag-and-drop-source p-0{% endblock %}
|
||||
{% block box_body %}
|
||||
<div class="external-events" data-method="{{ source.method }}" data-route="{{ path(source.route, source.routeParams) }}" data-route-replacer="{{ source.routeReplacer|json_encode|e('html_attr') }}">
|
||||
<div class="list-group list-group-flush external-events" data-method="{{ source.method }}" data-route="{{ path(source.route, source.routeParams) }}" data-route-replacer="{{ source.routeReplacer|json_encode|e('html_attr') }}">
|
||||
{% for entry in source.entries|slice(0, config.dragDropAmount) %}
|
||||
<div draggable="true" style="background-color: {{ entry.color }}; color: {{ entry.color|font_contrast }}" class="external-event ui-draggable ui-draggable-handle" data-entry="{{ entry.data|json_encode|e('html_attr') }}"{% if entry.project is not null %} data-toggle="tooltip" title="{{ entry.project.customer.name }}"{% endif %}>
|
||||
{% if source.blockInclude is not null and entry.blockName is not null and block(entry.blockName, source.blockInclude) is defined %}
|
||||
{{ block(entry.blockName, source.blockInclude) }}
|
||||
{% else %}
|
||||
<span class="title">{{ entry.title }}</span>
|
||||
{% endif %}
|
||||
<div class="p-1 ps-2 list-group-item external-event draggable" data-entry="{{ entry.data|json_encode|e('html_attr') }}"{% if entry.project is not null %} data-toggle="tooltip" title="{{ entry.project.customer.name }}"{% endif %}>
|
||||
<div class="row align-items-center">
|
||||
<div class="col text-truncate">
|
||||
{% if source.blockInclude is not null and entry.blockName is not null and block(entry.blockName, source.blockInclude) is defined %}
|
||||
{{ block(entry.blockName, source.blockInclude) }}
|
||||
{% else %}
|
||||
<span class="d-block text-muted text-truncate mt-n1">{{ entry.title }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -44,9 +41,9 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-xs-12 {% if hasTwoColumns %}col-sm-7 col-md-8 col-lg-9 col-print-12{% endif %}">
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
<div class="col-xs-12 {% if hasTwoColumns %}col-md-8 col-lg-9 col-print-12{% endif %}">
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_body %}
|
||||
<div id="timesheet_calendar"></div>
|
||||
{% endblock %}
|
||||
@@ -66,481 +63,140 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{% set calendarSelector = '#timesheet_calendar' %}
|
||||
{% set calendarSelector = 'timesheet_calendar' %}
|
||||
{% set createParams = '' %}
|
||||
{% set createRoute = 'timesheet_create' %}
|
||||
{% set editRoute = 'timesheet_edit' %}
|
||||
{% set canDelete = is_granted('delete_own_timesheet') %}
|
||||
{% set canCreate = is_granted('create_own_timesheet') %}
|
||||
{% set canEdit = is_granted('edit_own_timesheet') %}
|
||||
{% set isForeignEdit = false %}
|
||||
{% if user != app.user %}
|
||||
{% set isForeignEdit = true %}
|
||||
{% set createParams = '&user=' ~ user.id %}
|
||||
{% set createParams = 'user=' ~ user.id ~ '&' %}
|
||||
{% set createRoute = 'admin_timesheet_create' %}
|
||||
{% set editRoute = 'admin_timesheet_edit' %}
|
||||
{% set canDelete = is_granted('delete_other_timesheet') %}
|
||||
{% set canCreate = is_granted('create_other_timesheet') %}
|
||||
{% set canEdit = is_granted('edit_other_timesheet') %}
|
||||
{% endif %}
|
||||
|
||||
{{ parent() }}
|
||||
<script>
|
||||
activatePopover();
|
||||
{# using var, so it can be re-used in the different scopes #}
|
||||
var calendar = null;
|
||||
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
KimaiReloadPageWidget.create('kimai.systemConfigUpdate', true);
|
||||
});
|
||||
|
||||
document.addEventListener('kimai.timesheetUpdate', function() {
|
||||
jQuery('{{ calendarSelector }}').fullCalendar('refetchEvents');
|
||||
});
|
||||
|
||||
{# https://gomakethings.com/dynamically-changing-the-text-color-based-on-background-color-contrast-with-vanilla-js/ #}
|
||||
function calculateFontContrastColor(hexcolor)
|
||||
{
|
||||
if (hexcolor.slice(0, 1) === '#') {
|
||||
hexcolor = hexcolor.slice(1);
|
||||
}
|
||||
|
||||
if (hexcolor.length === 3) {
|
||||
hexcolor = hexcolor.split('').map(function (hex) { return hex + hex; }).join('');
|
||||
}
|
||||
|
||||
let r = parseInt(hexcolor.substr(0,2),16);
|
||||
let g = parseInt(hexcolor.substr(2,2),16);
|
||||
let b = parseInt(hexcolor.substr(4,2),16);
|
||||
let yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
|
||||
|
||||
return (yiq >= 128) ? '#000000' : '#ffffff';
|
||||
}
|
||||
|
||||
function convertApiTimesheetToCalendar(apiItem)
|
||||
{
|
||||
const defaultColor = kimai.getConfiguration().get('defaultColor');
|
||||
let color = apiItem.activity.color;
|
||||
if (color === null || color === defaultColor) {
|
||||
color = apiItem.project.color;
|
||||
if (color === null || color === defaultColor) {
|
||||
color = apiItem.project.customer.color;
|
||||
}
|
||||
}
|
||||
if (color == null) {
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
let title = apiItem.activity.name;
|
||||
|
||||
let description = '';
|
||||
if (apiItem.description !== null && apiItem.description !== '') {
|
||||
description = '{{ constant('\\App\\Form\\Type\\CalendarTitlePatternType::SPACER') }}' + apiItem.description;
|
||||
}
|
||||
|
||||
{% if config.entryTitlePattern == constant('\\App\\Form\\Type\\CalendarTitlePatternType::PATTERN_PROJECT') %}
|
||||
title = apiItem.project.name;
|
||||
{% elseif config.entryTitlePattern == constant('\\App\\Form\\Type\\CalendarTitlePatternType::PATTERN_CUSTOMER') %}
|
||||
title = apiItem.project.customer.name;
|
||||
{% elseif config.entryTitlePattern == constant('\\App\\Form\\Type\\CalendarTitlePatternType::PATTERN_DESCRIPTION') %}
|
||||
title = apiItem.description;
|
||||
{% elseif config.entryTitlePattern == constant('\\App\\Form\\Type\\CalendarTitlePatternType::PATTERN_ACTIVITY_DESCRIPTION') %}
|
||||
title = apiItem.activity.name + description;
|
||||
{% elseif config.entryTitlePattern == constant('\\App\\Form\\Type\\CalendarTitlePatternType::PATTERN_PROJECT_DESCRIPTION') %}
|
||||
title = apiItem.project.name + description;
|
||||
{% elseif config.entryTitlePattern == constant('\\App\\Form\\Type\\CalendarTitlePatternType::PATTERN_CUSTOMER_DESCRIPTION') %}
|
||||
title = apiItem.project.customer.name + description;
|
||||
{% elseif config.entryTitlePattern == constant('\\App\\Form\\Type\\CalendarTitlePatternType::PATTERN_PROJECT_CUSTOMER') %}
|
||||
title = apiItem.project.name + apiItem.project.customer.name;
|
||||
{% endif %}
|
||||
|
||||
if (title === '' || title === null) {
|
||||
title = apiItem.activity.name;
|
||||
}
|
||||
|
||||
return {
|
||||
id: apiItem.id,
|
||||
title: title,
|
||||
description: apiItem.description,
|
||||
start: apiItem.begin,
|
||||
end: apiItem.end,
|
||||
activity: apiItem.activity.name,
|
||||
project: apiItem.project.name,
|
||||
customer: apiItem.project.customer.name,
|
||||
tags: apiItem.tags,
|
||||
color: color,
|
||||
textColor: calculateFontContrastColor(color),
|
||||
};
|
||||
}
|
||||
|
||||
function renderEventPopoverTitle(eventObj)
|
||||
{
|
||||
return eventObj.start.format('L') + ' | ' + eventObj.start.format('LT') + ' - ' + (eventObj.end ? eventObj.end.format('LT') : '');
|
||||
}
|
||||
|
||||
function renderEventPopoverContent(eventObj)
|
||||
{
|
||||
const escaper = kimai.getPlugin('escape');
|
||||
|
||||
return '<div class="calendar-entry">' +
|
||||
'<ul>' +
|
||||
'<li>' + '{{ 'label.customer'|trans }}: ' + escaper.escapeForHtml(eventObj.customer) + '</li>' +
|
||||
'<li>' + '{{ 'label.project'|trans }}: ' + escaper.escapeForHtml(eventObj.project) + '</li>' +
|
||||
'<li>' + '{{ 'label.activity'|trans }}: ' + escaper.escapeForHtml(eventObj.activity) + '</li>' +
|
||||
'</ul>' +
|
||||
(eventObj.description !== null || eventObj.tags.length > 0 ? '<hr>' : '') +
|
||||
(eventObj.description ? '<p>' + eventObj.description + '</p>' : '') +
|
||||
(eventObj.tags !== null && eventObj.tags.length > 0 ? '<span class="badge bg-green">' + eventObj.tags.join('</span> <span class="badge bg-green">') + '</span>' : '') +
|
||||
'</div>'
|
||||
;
|
||||
}
|
||||
|
||||
function showPopover()
|
||||
{
|
||||
return window._hidePopovers === false;
|
||||
}
|
||||
|
||||
function activatePopover()
|
||||
{
|
||||
window._hidePopovers = false;
|
||||
}
|
||||
|
||||
function deactivatePopover()
|
||||
{
|
||||
window._hidePopovers = true;
|
||||
}
|
||||
|
||||
function hidePopover()
|
||||
{
|
||||
jQuery('.popover').popover('hide');
|
||||
}
|
||||
|
||||
let changeHandler = function(event, delta, revertFunc) {
|
||||
let updateUrl = '{{ path('patch_timesheet', {id: '-XX-'}) }}'.replace('-XX-', event.id);
|
||||
let API = kimai.getPlugin('api');
|
||||
let ALERT = kimai.getPlugin('alert');
|
||||
|
||||
let payload = {'begin': event.start.format()};
|
||||
|
||||
if (event.end !== null && event.end !== undefined) {
|
||||
payload.end = event.end.format();
|
||||
} else {
|
||||
payload.end = null;
|
||||
}
|
||||
|
||||
API.patch(updateUrl, JSON.stringify(payload), function(result) {
|
||||
ALERT.success('action.update.success');
|
||||
}, function(xhr, err) {
|
||||
const callback = API.getPatchErrorHandler();
|
||||
revertFunc();
|
||||
callback(xhr, err);
|
||||
});
|
||||
};
|
||||
const reloader = function() { calendar.reloadEvents(); };
|
||||
document.addEventListener('kimai.timesheetUpdate', reloader);
|
||||
document.addEventListener('kimai.timesheetDelete', reloader);
|
||||
|
||||
document.addEventListener('kimai.initialized', function(event) {
|
||||
// prevent multiple open popovers
|
||||
jQuery('html').on('click', function(e) {
|
||||
jQuery('.popover').each(function() {
|
||||
if(jQuery(e.target).parents(".fc-time-grid-event").get(0) !== $(this).prev().get(0)) {
|
||||
jQuery(this).popover('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
const kimai = event.detail.kimai;
|
||||
|
||||
{# convert HTML to draggable items #}
|
||||
jQuery('.external-events .external-event').each(function () {
|
||||
{# FullCalendar 3 can only work with jQuery UI elements #}
|
||||
jQuery(this).draggable({
|
||||
zIndex : 1070,
|
||||
revert : true, {# will cause the event to go back to its #}
|
||||
revertDuration: 0 {# original position after the drag #}
|
||||
});
|
||||
});
|
||||
|
||||
let kimai = event.detail.kimai;
|
||||
let API = kimai.getPlugin('api');
|
||||
let DATES = kimai.getPlugin('date');
|
||||
jQuery('{{ calendarSelector }}').fullCalendar({
|
||||
themeSystem: 'bootstrap3',
|
||||
defaultView: '{{ app.user.getPreferenceValue('calendar.initial_view') }}',
|
||||
let calendarOptions = {
|
||||
dragdrop: {
|
||||
container: '.external-events',
|
||||
items: '.external-event',
|
||||
},
|
||||
initialView: '{{ app.user.getPreferenceValue('calendar_initial_view') }}',
|
||||
translations: {
|
||||
customer: '{{ 'customer'|trans }}',
|
||||
project: '{{ 'project'|trans }}',
|
||||
activity: '{{ 'activity'|trans }}',
|
||||
},
|
||||
permissions: {
|
||||
edit: {% if canEdit %}true{% else %}false{% endif %},
|
||||
create: {% if canCreate %}true{% else %}false{% endif %},
|
||||
edit_begin: {% if can_edit_begin %}true{% else %}false{% endif %},
|
||||
edit_end: {% if can_edit_end %}true{% else %}false{% endif %},
|
||||
edit_duration: {% if can_edit_duration %}true{% else %}false{% endif %},
|
||||
punch: {% if is_punch_mode %}true{% else %}false{% endif %},
|
||||
},
|
||||
icons: {
|
||||
next: '{{ 'right'|icon(true) }}',
|
||||
previous: '{{ 'left'|icon(true) }}',
|
||||
nextYear: '{{ 'right2'|icon(true) }}',
|
||||
previousYear: '{{ 'left2'|icon(true) }}',
|
||||
},
|
||||
{% if google is not null %}
|
||||
googleCalendarApiKey: '{{ google.apiKey }}',
|
||||
{% endif %}
|
||||
header : {
|
||||
left : 'prev,next today{% if canDelete %} deleteButton{% endif %}',
|
||||
center: 'title',
|
||||
right : 'month,agendaWeek,agendaDay'
|
||||
patterns: {
|
||||
title: '{{ config.entryTitlePattern }}',
|
||||
},
|
||||
dragRevertDuration: 0,
|
||||
eventLimit: {{ config.dayLimit }},
|
||||
weekNumbersWithinDays: true,
|
||||
weekNumberCalculation: 'ISO',
|
||||
weekNumbers: {% if config.showWeekNumbers %}true{% else %}false{% endif %},
|
||||
allDaySlot: false,
|
||||
navLinks: true,
|
||||
locale: kimai.getConfiguration().get('locale').replace('_', '-').toLowerCase(),
|
||||
firstDay: kimai.getConfiguration().get('first_dow_iso'),
|
||||
customButtons: {
|
||||
deleteButton: {
|
||||
text: '{{ 'action.delete'|trans }}',
|
||||
click: function() {
|
||||
kimai.getPlugin('alert').info('{{ 'calendar.drag_and_drop.delete'|trans }}');
|
||||
}
|
||||
}
|
||||
},
|
||||
height: 'auto',
|
||||
nowIndicator: true,
|
||||
defaultStartTime: {% if defaultStartTime is null %}null{% else %}'{{ defaultStartTime }}'{% endif %},
|
||||
now: '{{ now|date_format('c') }}',
|
||||
{# see https://github.com/kimai/kimai/issues/2155 #}
|
||||
timezone: '{{ app.user.timezone == 'UTC' ? 'GMT' : app.user.timezone }}',
|
||||
weekends: {% if config.showWeekends %}true{% else %}false{% endif %},
|
||||
businessHours: {
|
||||
dow: [{{ config.businessDays|join(',') }}],
|
||||
start: '{{ config.businessTimeBegin }}',
|
||||
end: '{{ config.businessTimeEnd }}'
|
||||
},
|
||||
slotDuration: '{{ config.slotDuration }}',
|
||||
slotLabelInterval: '{{ config.slotDuration }}',
|
||||
minTime: '{{ config.timeframeBegin }}',
|
||||
maxTime: '{{ config.timeframeEnd }}',
|
||||
defaultTimedEventDuration: '{{ config.slotDuration }}', {# how long should entries look like when they don't have an end #}
|
||||
droppable: true,
|
||||
{# drop function handles external draggable events #}
|
||||
drop: function (date, jsEvent, ui, resourceId) {
|
||||
let entry = jsEvent.target;
|
||||
let source = entry.parentElement;
|
||||
let plainData = entry.dataset.entry;
|
||||
let data = JSON.parse(plainData);
|
||||
{% if isForeignEdit and canCreate %}
|
||||
data.user = {{ user.id }};
|
||||
{% endif %}
|
||||
let urlReplacer = JSON.parse(source.dataset.routeReplacer);
|
||||
let apiUrl = source.dataset.route;
|
||||
|
||||
for (const [key, value] of Object.entries(urlReplacer)) {
|
||||
apiUrl = apiUrl.replace(key, data[value]);
|
||||
}
|
||||
|
||||
let view = jQuery('{{ calendarSelector }}').fullCalendar('getView');
|
||||
let begin = date.clone();
|
||||
|
||||
{#
|
||||
FIXME some tracking modes do not allow to set the time
|
||||
|
||||
public function canEditEnd(): bool;
|
||||
public function canEditDuration(): bool;
|
||||
public function canUpdateTimesWithAPI(): bool;
|
||||
|
||||
#}
|
||||
|
||||
{% if not can_edit_begin %}
|
||||
begin.time('{{ defaultStartTime }}');
|
||||
{% endif %}
|
||||
|
||||
if (view.name === 'month' && !begin.hasTime()) {
|
||||
begin.time('{{ defaultStartTime }}');
|
||||
}
|
||||
|
||||
let end = begin.clone();
|
||||
end.add(moment.duration('{{ config.slotDuration }}'));
|
||||
|
||||
{% if not is_punch_mode %}
|
||||
{% if can_edit_begin %}
|
||||
data.begin = begin.format();
|
||||
{% endif %}
|
||||
{% if can_edit_end %}
|
||||
data.end = end.format();
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
if (source.dataset.method === 'PATCH') {
|
||||
API.patch(
|
||||
apiUrl,
|
||||
JSON.stringify(data),
|
||||
function (result) {
|
||||
kimai.getPlugin('alert').success('action.update.success');
|
||||
let newItem = convertApiTimesheetToCalendar(result);
|
||||
jQuery('{{ calendarSelector }}').fullCalendar('renderEvent', newItem);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
API.post(
|
||||
apiUrl,
|
||||
JSON.stringify(data),
|
||||
function (result) {
|
||||
kimai.getPlugin('alert').success('action.update.success');
|
||||
let newItem = convertApiTimesheetToCalendar(result);
|
||||
jQuery('{{ calendarSelector }}').fullCalendar('renderEvent', newItem);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
eventSources: [
|
||||
{
|
||||
events: function(start, end, timezone, callback) {
|
||||
let from = moment(start.format()).format(moment.HTML5_FMT.DATETIME_LOCAL_SECONDS);
|
||||
let to = moment(end.format()).format(moment.HTML5_FMT.DATETIME_LOCAL_SECONDS);
|
||||
API.get('{{ path('get_timesheets') }}?user={{ user.id }}&size=1000&full=true&begin='+from+'&end='+to, {}, function(result) {
|
||||
let apiEvents = [];
|
||||
for (const record of result) {
|
||||
apiEvents.push(convertApiTimesheetToCalendar(record));
|
||||
}
|
||||
callback(apiEvents);
|
||||
});
|
||||
id: 'kimaiUserTimeSource',
|
||||
type: 'timesheet',
|
||||
url: '{{ path('get_timesheets') }}?user={{ user.id }}&size=1000&full=true&begin={from}&end={to}',
|
||||
options: {
|
||||
color: '{{ config('theme.calendar.background_color') }}',
|
||||
},
|
||||
color: '{{ config('theme.calendar.background_color') }}',
|
||||
name: 'kimaiUserTimeSource'
|
||||
}
|
||||
{% if google is not null %}
|
||||
{% for source in google.sources %}
|
||||
,
|
||||
{
|
||||
{% for source in google.sources %}
|
||||
,
|
||||
{
|
||||
id: 'googleCompanyCalendar',
|
||||
type: 'google',
|
||||
options: {
|
||||
googleCalendarId: '{{ source.uri }}',
|
||||
name: '{{ source.id }}',
|
||||
color: '{{ source.color }}',
|
||||
textColor: '{{ source.color|font_contrast }}',
|
||||
editable: false
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
],
|
||||
eventRender: function(eventObj, $el, view) {
|
||||
{# don't show popover for google calendar #}
|
||||
if (eventObj.source.ajaxSettings !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
{# or when an event is dragged or resized #}
|
||||
if (!showPopover()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.name !== 'month' ) {
|
||||
jQuery(".fc-dailytotal").text(DATES.formatSeconds(0)).data('seconds', 0).data('ids', '');
|
||||
}
|
||||
|
||||
hidePopover();
|
||||
$el.popover({
|
||||
title: renderEventPopoverTitle(eventObj),
|
||||
content: renderEventPopoverContent(eventObj),
|
||||
trigger: 'hover',
|
||||
placement: 'auto',
|
||||
container: 'body',
|
||||
html: true
|
||||
});
|
||||
},
|
||||
viewRender: function(view, element) {
|
||||
if (view.name === 'month') {
|
||||
return;
|
||||
}
|
||||
|
||||
{# allowed in agendaWeek and agendaDay #}
|
||||
jQuery(".fc-day-header").each(function (key, val) {
|
||||
var $this = jQuery(this);
|
||||
var dateYMD = $this.attr("data-date");
|
||||
$this.append('<div class="fc-dailytotal" data-seconds="0" data-ids="" id="dailytotal-' + dateYMD + '">'+DATES.formatSeconds(0)+'</div>');
|
||||
});
|
||||
},
|
||||
eventAfterRender: function(event, element, view) {
|
||||
{# allowed in agendaWeek and agendaDay #}
|
||||
if (view.name === 'month') {
|
||||
return;
|
||||
}
|
||||
|
||||
{# TODO calculate duration for running entries #}
|
||||
if (event.end === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
{# TODO split day entries are calculated only for the begin date #}
|
||||
var $element = jQuery("#dailytotal-" + moment(event.start).format("YYYY-MM-DD"));
|
||||
var ids = $element.data('ids');
|
||||
if (ids === null || ids === undefined) {
|
||||
ids = '';
|
||||
}
|
||||
|
||||
if (ids.indexOf(event.id) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var prev = $element.data('seconds');
|
||||
var duration = prev + ((event.end - event.start) / 1000);
|
||||
$element.data('seconds', duration);
|
||||
$element.data('ids', event.id + ' ' + ids);
|
||||
$element.text(DATES.formatSeconds(duration));
|
||||
},
|
||||
{% if not is_punch_mode and canCreate %}
|
||||
dayClick: function(date, jsEvent, view) {
|
||||
{#
|
||||
Day-clicks are always triggered, unless a selection was created.
|
||||
So clicking in a day (month view) or any slot (week and day view) will trigger a dayClick
|
||||
BEFORE triggering a select - make sure not two create dialogs are requested
|
||||
#}
|
||||
if (view.type !== 'month') {
|
||||
return;
|
||||
}
|
||||
|
||||
let createUrl = '{{ path(createRoute) }}?begin=' + date.format() + '{{ createParams|raw }}';
|
||||
kimai.getPlugin('modal').openUrlInModal(createUrl);
|
||||
url: {
|
||||
update: (timesheetId) => {
|
||||
return '{{ path('patch_timesheet', {id: 1}) }}'.replace('1', timesheetId);
|
||||
},
|
||||
selectable: true,
|
||||
select: function(start, end, jsEvent, view) {
|
||||
if(view.type === 'month') {
|
||||
{#
|
||||
Multi-day clicks are NOT allowed in the month view, as simple day clicks would also trigger
|
||||
a select - there is no way to distinguish a simple click and a two day selection
|
||||
#}
|
||||
return;
|
||||
create: (start, end) => {
|
||||
let createUrl = '{{ path(createRoute) }}' + '?{{ createParams|raw }}';
|
||||
if (end !== undefined) {
|
||||
createUrl += 'from=' + start + '&to=' + end;
|
||||
} else {
|
||||
createUrl += 'begin=' + start;
|
||||
}
|
||||
let createUrl = '{{ path(createRoute) }}' + '?from=' + start.format() + '&to=' + end.format() + '{{ createParams|raw }}';
|
||||
kimai.getPlugin('modal').openUrlInModal(createUrl);
|
||||
return createUrl;
|
||||
},
|
||||
{% endif %}
|
||||
{% if canEdit %}
|
||||
eventClick: function(eventObj, jsEvent, view) {
|
||||
if (eventObj.source.ajaxSettings !== undefined) {
|
||||
jsEvent.preventDefault();
|
||||
return;
|
||||
}
|
||||
let editUrl = '{{ path(editRoute, {id: '-XX-'}) }}'.replace('-XX-', eventObj.id);
|
||||
kimai.getPlugin('modal').openUrlInModal(editUrl);
|
||||
edit: (timesheetId) => {
|
||||
return '{{ path(editRoute, {id: '-XX-'}) }}'.replace('-XX-', timesheetId);
|
||||
},
|
||||
{% if not is_punch_mode %}
|
||||
editable: true,
|
||||
eventDragStart: function(event, jsEvent, ui, view) {
|
||||
deactivatePopover();
|
||||
},
|
||||
eventDragStop: function(event, jsEvent, ui, view) {
|
||||
activatePopover();
|
||||
{% if canDelete %}
|
||||
var trash = jQuery('.fc-deleteButton-button');
|
||||
var offset = trash.offset();
|
||||
|
||||
if (jsEvent.pageX >= offset.left && jsEvent.pageX<= (offset.left + trash.outerWidth(true)) &&
|
||||
jsEvent.pageY >= offset.top && jsEvent.pageY <= (offset.top + trash.outerHeight(true))) {
|
||||
API.delete(
|
||||
'{{ path('delete_timesheet', {'id': '__xxx__'}) }}'.replace('__xxx__', event.id),
|
||||
function () {
|
||||
kimai.getPlugin('alert').success('action.delete.success');
|
||||
jQuery('{{ calendarSelector }}').fullCalendar('removeEvents', event._id);
|
||||
}, function () {
|
||||
kimai.getPlugin('alert').success('action.delete.error');
|
||||
}
|
||||
);
|
||||
}
|
||||
{% endif %}
|
||||
},
|
||||
eventDrop: changeHandler,
|
||||
eventResizeStart: function(event, jsEvent, ui, view) {
|
||||
deactivatePopover();
|
||||
},
|
||||
eventResizeStop: function(event, jsEvent, ui, view) {
|
||||
activatePopover();
|
||||
},
|
||||
eventResize: changeHandler,
|
||||
actions: (timesheetId) => {
|
||||
return '{{ path('get_timesheet_actions', {id: 1, 'view': 'calendar', 'locale': (app.request.locale)}) }}'.replace('1', timesheetId);
|
||||
},
|
||||
},
|
||||
preparePayloadForUpdate: (data) => {
|
||||
{% if isForeignEdit %}
|
||||
data.user = {{ user.id }};
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
});
|
||||
var trashBtn = jQuery('.fc-deleteButton-button');
|
||||
trashBtn.html('<span class="text-danger"><i class="{{ 'trash'|icon }} text-danger"></i> ' + trashBtn.text() + '</span>');
|
||||
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
calendar = new KimaiCalendar(
|
||||
kimai,
|
||||
document.getElementById('{{ calendarSelector }}'),
|
||||
Object.assign({}, calendarOptions, {{ config|json_encode|raw }})
|
||||
);
|
||||
calendar.render();
|
||||
|
||||
document.querySelector('.fc-today-button').classList.remove('btn-icon');
|
||||
document.querySelector('.fc-header-toolbar').classList.toggle('row');
|
||||
document.querySelector('.fc-header-toolbar').classList.add('p-3');
|
||||
const toolbar = document.querySelectorAll('.fc-header-toolbar .fc-toolbar-chunk');
|
||||
toolbar[0].parentElement.classList.add('p-3');
|
||||
toolbar[0].classList.add('col-md-4', 'col-6', 'mb-1', 'text-start', 'order-xs-1');
|
||||
toolbar[1].classList.add('col-md-4', 'col-12', 'mb-1', 'text-center', 'order-3');
|
||||
toolbar[2].classList.add('col-md-4', 'col-6', 'mb-1', 'text-end', 'order-md-4', 'order-xs-2');
|
||||
|
||||
KimaiReloadPageWidget.create('kimai.systemConfigUpdate', true);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,7 +1,7 @@
|
||||
{% macro dd_event(title, customer, project, deprecated) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
<span class="title">{{ title }}</span>
|
||||
{{ title }}
|
||||
{% if project is not null %}
|
||||
<span class="subtitle">{{ project }}{% if customer is not null %}, {{ customer }}{% endif %}</span>
|
||||
<small class="d-block text-muted text-truncate mt-n1">{{ project }}{% if customer is not null %}, {{ customer }}{% endif %}</small>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
{% macro customers(view, query) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'customers', view, {'query': query}) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro customer(customer, view, options) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'customer', view, {'customer': customer}) %}
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'customers'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customer(customer, 'delete') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% set inUse = (stats.counter > 0) %}
|
||||
|
||||
{% set params = {
|
||||
'%records%': stats.counter,
|
||||
'%duration%': stats.duration|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,
|
||||
'used': (stats.counter > 0),
|
||||
'back': path('admin_customer')
|
||||
}) }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_actions %}{{ actions.customer(customer, 'customer_details') }}{% endblock %}
|
||||
{% block page_title %}{{ 'customers'|trans }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% set can_edit = is_granted('edit', customer) %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_attributes %}id="customer_details_box"{% endblock %}
|
||||
{% block box_title %}
|
||||
@@ -17,26 +13,27 @@
|
||||
{% endblock %}
|
||||
{% block box_tools %}
|
||||
{% if can_edit %}
|
||||
<a class="modal-ajax-form open-edit btn btn-default btn-sm" 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>
|
||||
{{ widgets.card_tool_button('vcard', {'title': 'vcard', 'translation_domain': 'actions', 'icon': 'far fa-address-card', 'url': path('customer_vcard', {'id': customer.id})}) }}
|
||||
{{ widgets.card_tool_button('edit', {'class': 'modal-ajax-form open-edit', 'title': 'action.edit', 'url': path('admin_customer_edit', {'id': customer.id})}) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if customer.comment is not empty %}
|
||||
<div class="comment">
|
||||
<div class="comment p-3">
|
||||
{{ customer.comment|comment2html(true) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<table class="table table-hover dataTable">
|
||||
{% if not customer.visible %}
|
||||
<tr class="{{ widgets.class_customer_row(customer, now) }}">
|
||||
<th>{{ 'label.visible'|trans }}</th>
|
||||
<th>{{ 'visible'|trans }}</th>
|
||||
<td>{{ widgets.label_boolean(customer.visible) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if not customer.billable %}
|
||||
<tr>
|
||||
<th>{{ 'label.billable'|trans }}</th>
|
||||
<th>{{ 'billable'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ widgets.label_boolean(customer.billable) }}
|
||||
</td>
|
||||
@@ -44,74 +41,74 @@
|
||||
{% endif %}
|
||||
{% if customer.address is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.address'|trans }}</th>
|
||||
<th>{{ 'address'|trans }}</th>
|
||||
<td>{{ customer.address|nl2br }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if customer.country is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.country'|trans }}</th>
|
||||
<td>{{ customer.country|country }}</td>
|
||||
<th>{{ 'country'|trans }}</th>
|
||||
<td>{{ customer.country|country_name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if customer.contact is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.contact'|trans }}</th>
|
||||
<th>{{ 'contact'|trans }}</th>
|
||||
<td>{{ customer.contact }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if customer.phone is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.phone'|trans }}</th>
|
||||
<th>{{ '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>
|
||||
<th>{{ '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>
|
||||
<th>{{ '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>
|
||||
<th>{{ '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>
|
||||
<th>{{ 'fax'|trans }}</th>
|
||||
<td>{{ customer.fax }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if customer.timezone is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.timezone'|trans }}</th>
|
||||
<td>{{ customer_now|date_full }} ({{ customer.timezone }})</td>
|
||||
<th>{{ 'timezone'|trans }}</th>
|
||||
<td>{{ customer_now|date_time }} ({{ customer.timezone }})</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if customer.currency is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.currency'|trans }}</th>
|
||||
<th>{{ '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>
|
||||
<th>{{ 'number'|trans }}</th>
|
||||
<td>{{ customer.number }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if customer.vatId is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.vat_id'|trans }}</th>
|
||||
<th>{{ 'vat_id'|trans }}</th>
|
||||
<td>{{ customer.vatId }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -127,7 +124,11 @@
|
||||
{% endembed %}
|
||||
|
||||
{{ render(controller('App\\Controller\\CustomerController::projectsAction', {'customer': customer.id, 'page': 1})) }}
|
||||
|
||||
|
||||
{% if can_edit %}
|
||||
{{ include('embeds/rates-table.html.twig', {'id': 'customer_rates_box', 'entity': customer, 'create_url': path('admin_customer_rate_add', {'id': customer.id}), 'delete_route': 'delete_customer_rate', 'currency': customer.currency, 'edit_route': 'admin_customer_rate_edit'}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if stats is not null %}
|
||||
{{ include('embeds/budgets.html.twig', {'entity': customer, 'stats': stats, 'currency': customer.currency}) }}
|
||||
{% endif %}
|
||||
@@ -136,10 +137,6 @@
|
||||
{{ render(controller(controller, {'customer': customer, 'page': 1})) }}
|
||||
{% endfor %}
|
||||
|
||||
{% if can_edit %}
|
||||
{{ include('embeds/rates-table.html.twig', {'id': 'customer_rates_box', 'entity': customer, 'create_url': path('admin_customer_rate_add', {'id': customer.id}), 'delete_route': 'delete_customer_rate', 'currency': customer.currency}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if teams is not null %}
|
||||
{% set options = {'teams': teams, 'team': team} %}
|
||||
{% if is_granted('permissions', customer) %}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'customers'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customer(customer, 'edit') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% 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': (customer.id is null ? 'create'|trans : 'edit'|trans({}, 'actions')),
|
||||
'form': form,
|
||||
@@ -14,10 +10,10 @@
|
||||
{% embed formEditTemplate with formOptions %}
|
||||
{% block form_body %}
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<div class="col-md-9">
|
||||
{{ form_row(form.name) }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.color) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,6 +80,13 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.teams is defined %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{{ form_row(form.teams) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{{ form_row(form.visible) }}
|
||||
@@ -93,11 +96,22 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if form.metaFields is defined and form.metaFields is not empty %}
|
||||
{% for meta in form.metaFields|sort((a, b) => a.vars.data.order <=> b.vars.data.order) %}
|
||||
{{ form_row(meta) }}
|
||||
{% endfor %}
|
||||
{{ form_row(form.metaFields) }}
|
||||
{% endif %}
|
||||
{{ form_widget(form) }}
|
||||
{% embed '@theme/embeds/card.html.twig' with {collapsed: true} %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'invoices'|trans }}{% endblock %}
|
||||
{% block box_body_class %}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if form.invoiceTemplate is defined %}
|
||||
{{ form_row(form.invoiceTemplate) }}
|
||||
{% endif %}
|
||||
{% if form.invoiceText is defined %}
|
||||
{{ form_row(form.invoiceText) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{{ form_rest(form) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'customer': customer, 'projects': projects, 'page': page} %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {'customer': customer, 'projects': projects, 'page': page} %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'projects'|trans }}{% endblock %}
|
||||
{% block box_attributes %}
|
||||
id="project_list_box" data-href="{{ path('customer_projects', {'id': customer.id}) }}" data-reload="kimai.projectUpdate kimai.projectDelete"
|
||||
id="project_list_box" data-reload="kimai.projectUpdate kimai.projectDelete"
|
||||
{% endblock %}
|
||||
{% block box_tools %}
|
||||
{% if is_granted('view_reporting') and is_granted('budget_any', 'project') %}
|
||||
<a class="btn btn-default btn-sm btn-pager" href="{{ path('report_project_view', {'customer': customer.id}) }}" data-toggle="tooltip" data-placement="top" title="{{ 'report_project_view'|trans({}, 'reporting') }}"><i class="{{ 'reporting'|icon }}"></i></a>
|
||||
{% endif %}
|
||||
{% if customer.visible and is_granted('create_project') %}
|
||||
<a class="modal-ajax-form open-edit btn btn-default btn-sm btn-pager" data-href="{{ path('admin_project_create_with_customer', {'customer': customer.id}) }}" data-toggle="tooltip" data-placement="top" title="{{ 'create'|trans }}"><i class="{{ 'create'|icon }}"></i></a>
|
||||
{% endif %}
|
||||
{% if projects|length > 0 %}
|
||||
{{ pagination(projects, { css_container_class: 'pagination pagination-sm inline', routeName: 'customer_projects', routeParams: {'id': customer.id} }) }}
|
||||
{% endif %}
|
||||
{%- if is_granted('report:project') and is_granted('budget_any', 'project') -%}
|
||||
{{ widgets.card_tool_button('reporting', {'title': 'report_project_view', 'translation_domain': 'reporting', 'url': path('report_project_view', {'customer': customer.id})}) }}
|
||||
{%- endif -%}
|
||||
{%- if customer.visible and is_granted('create_project') -%}
|
||||
{{ widgets.card_tool_button('create', {'class': 'modal-ajax-form open-edit', 'title': 'create', 'url': path('admin_project_create_with_customer', {'customer': customer.id})}) }}
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_tools_attributes %}data-page="{{ page }}"{% endblock %}
|
||||
{% block box_footer %}
|
||||
{%- if projects|length > 0 -%}
|
||||
<div class="d-flex">
|
||||
{{ pagination(projects, { css_container_class: 'pagination ms-auto m-0', routeName: 'customer_projects', routeParams: {'id': customer.id} }) }}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}{%- if projects|length > 0 -%}p-0{%- endif -%}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if projects|length == 0 %}
|
||||
<div class="comment">{{ 'error.no_entries_found'|trans }}</div>
|
||||
@@ -25,11 +28,11 @@
|
||||
<table class="table table-hover dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.name'|trans }}</th>
|
||||
<th class="hidden-xs">{{ 'label.comment'|trans }}</th>
|
||||
<th class="w-min text-right">{{ 'label.project_end'|trans }}</th>
|
||||
<th class="w-min text-center">{{ 'label.visible'|trans }}</th>
|
||||
<th class="w-min text-center">{{ 'label.team'|trans }}</th>
|
||||
<th>{{ 'name'|trans }}</th>
|
||||
<th class="d-none d-md-table-cell">{{ 'comment'|trans }}</th>
|
||||
<th class="d-none d-sm-table-cell w-min text-end">{{ 'project_end'|trans }}</th>
|
||||
<th class="d-none d-sm-table-cell w-min text-center">{{ 'visible'|trans }}</th>
|
||||
<th class="d-none d-sm-table-cell w-min text-center">{{ 'team'|trans }}</th>
|
||||
<th class="actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -37,16 +40,16 @@
|
||||
{% for project in projects %}
|
||||
<tr {{ widgets.project_row_attr(project, now) }}>
|
||||
<td>{{ widgets.label_project(project) }}</td>
|
||||
<td class="hidden-xs">{{ project.comment|comment1line(not app.user.smallLayout) }}</td>
|
||||
<td class="w-min text-right">
|
||||
<td class="d-none d-md-table-cell">{{ project.comment|comment1line(false) }}</td>
|
||||
<td class="d-none d-sm-table-cell w-min text-end">
|
||||
{% if project.end is not null %}
|
||||
{{ project.end|date_short }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="w-min text-center">{{ widgets.label_visible(project.visible) }}</td>
|
||||
<td class="w-min text-center">{{ widgets.badge_team_access(project.teams) }}</td>
|
||||
<td class="d-none d-sm-table-cell w-min text-center">{{ widgets.label_visible(project.visible) }}</td>
|
||||
<td class="d-none d-sm-table-cell w-min text-center">{{ widgets.badge_team_access(project.teams) }}</td>
|
||||
<td class="actions">{{ actions.project(project, 'customer_details', true) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -56,7 +59,7 @@
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
<script type="text/javascript">
|
||||
{% set eventName = app.request.xmlHttpRequest ? 'kimai.reloadPage' : 'kimai.initialized' %}
|
||||
{% set eventName = kimai_context.javascriptRequest ? 'kimai.reloadPage' : 'kimai.initialized' %}
|
||||
document.addEventListener('{{ eventName }}', function() {
|
||||
KimaiPaginatedBoxWidget.create('#project_list_box');
|
||||
});
|
||||
|
||||
@@ -1,107 +1,59 @@
|
||||
{% 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 "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'name': {'class': 'alwaysVisible'},
|
||||
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
|
||||
'number': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'company': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'vat_id': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'contact': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'address': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'country': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'currency': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'phone': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'fax': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'mobile': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'email': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'homepage': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
} %}
|
||||
{% for field in metaColumns %}
|
||||
{% set columns = columns|merge({
|
||||
('mf_' ~ field.name): {'title': field.label|trans, 'class': 'hidden-xs hidden-sm mf_' ~ field.name, 'orderBy': false}
|
||||
}) %}
|
||||
{% endfor %}
|
||||
{% if is_granted('budget_money', 'customer') %}
|
||||
{% set columns = columns|merge({
|
||||
'budget': {'class': 'hidden-xs hidden-sm hidden text-right w-min', 'title': 'label.budget'|trans},
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget_time', 'customer') %}
|
||||
{% set columns = columns|merge({
|
||||
'timeBudget': {'class': 'hidden-xs hidden-sm hidden text-right w-min', 'title': 'label.timeBudget'|trans},
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% set columns = columns|merge({
|
||||
'team': {'class': 'text-center w-min', 'orderBy': false},
|
||||
'visible': {'class': 'text-center hidden w-min'},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
}) %}
|
||||
{% block datatable_row_attr %} {{ widgets.customer_row_attr(entry, now) }}{% endblock %}
|
||||
|
||||
{% set tableName = 'customer_admin' %}
|
||||
|
||||
{% block page_title %}{{ 'customers'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customers('index', query) }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.nothing_found('kimai.customerUpdate kimai.customerTeamUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.customerUpdate kimai.customerDelete kimai.customerTeamUpdate'}) }}
|
||||
|
||||
{% for entry in entries %}
|
||||
<tr {{ widgets.customer_row_attr(entry, now) }}>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
|
||||
{{ widgets.label_name(entry.name, entry.color) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'number') }}">{{ entry.number }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'company') }}">{{ entry.company }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'vat_id') }}">{{ entry.vatId }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'contact') }}">{{ entry.contact }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'address') }}">{{ entry.address }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'country') }}">{{ entry.country|country }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'currency') }}">{{ entry.currency|currency }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'phone') }}">{{ entry.phone }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'fax') }}">{{ entry.fax }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'mobile') }}">{{ entry.mobile }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'email') }}">{{ entry.email }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'homepage') }}">{{ entry.homepage }}</td>
|
||||
{% for field in metaColumns %}
|
||||
<td class="{{ 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, 'budget') }}">
|
||||
{% if entry.hasBudget() and is_granted('budget', entry) %}
|
||||
{{ entry.budget|money(entry.currency) }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'timeBudget') }}">
|
||||
{% if entry.hasTimeBudget() and is_granted('time', entry) %}
|
||||
{{ entry.timeBudget|duration }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
<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, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">{{ actions.customer(entry, 'index') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{{ tables.data_table_footer(entries, 'admin_customer_paginated') }}
|
||||
{% block datatable_column_value %}
|
||||
{% if column == 'name' %}
|
||||
{{ widgets.label_name(entry.name, entry.color|colorize(entry.name)) }}
|
||||
{% elseif column == 'comment' %}
|
||||
{{ entry.comment|comment1line }}
|
||||
{% elseif column == 'number' %}
|
||||
{{ entry.number }}
|
||||
{% elseif column == 'company' %}
|
||||
{{ entry.company }}
|
||||
{% elseif column == 'vat_id' %}
|
||||
{{ entry.vatId }}
|
||||
{% elseif column == 'contact' %}
|
||||
{{ entry.contact }}
|
||||
{% elseif column == 'address' %}
|
||||
{{ entry.address }}
|
||||
{% elseif column == 'country' %}
|
||||
{{ entry.country|country_name }}
|
||||
{% elseif column == 'currency' %}
|
||||
{{ entry.currency|currency_name }}
|
||||
{% elseif column == 'phone' %}
|
||||
{{ entry.phone }}
|
||||
{% elseif column == 'fax' %}
|
||||
{{ entry.fax }}
|
||||
{% elseif column == 'mobile' %}
|
||||
{{ entry.mobile }}
|
||||
{% elseif column == 'email' %}
|
||||
{{ entry.email }}
|
||||
{% elseif column == 'homepage' %}
|
||||
{{ entry.homepage }}
|
||||
{% elseif column == 'budget' %}
|
||||
{% if entry.hasBudget() and is_granted('budget', entry) %}
|
||||
{{ entry.budget|money(entry.currency) }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{% elseif column == 'timeBudget' %}
|
||||
{% if entry.hasTimeBudget() and is_granted('time', entry) %}
|
||||
{{ entry.timeBudget|duration }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{% elseif column == 'billable' %}
|
||||
{{ widgets.label_boolean(entry.billable) }}
|
||||
{% elseif column == 'team' %}
|
||||
{{ widgets.badge_team_access(entry.teams) }}
|
||||
{% elseif column == 'visible' %}
|
||||
{{ widgets.label_visible(entry.visible) }}
|
||||
{% elseif column == 'actions' %}
|
||||
{{ actions.customer(entry, 'index') }}
|
||||
{% elseif column starts with 'mf_' %}
|
||||
{{ widgets.meta_field_value(entry, data) }}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'customers'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customer(customer, 'permissions') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block main %}
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': ('permissions'|trans({}, 'actions')) ~ ': ' ~ customer.name,
|
||||
'form': form,
|
||||
'back': path('customer_details', {'id': customer.id})
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "customer/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'customers'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.customer(customer, 'rates') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block main %}
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': customer.name,
|
||||
'form': form,
|
||||
'back': path('admin_customer')
|
||||
|
||||
149
templates/dashboard/grid.html.twig
Normal file
149
templates/dashboard/grid.html.twig
Normal file
@@ -0,0 +1,149 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% from "macros/widgets.html.twig" import nothing_found, alert, page_actions, card_tool_button %}
|
||||
|
||||
{% block page_class %}dashboard{% endblock %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('chart') }}
|
||||
{{ encore_entry_link_tags('dashboard') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('chart') }}
|
||||
{{ encore_entry_script_tags('dashboard') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if widgets is empty %}
|
||||
{{ nothing_found() }}
|
||||
{% else %}
|
||||
{% if form is defined %}
|
||||
{{ alert('warning', 'dashboard.edit_mode') }}
|
||||
|
||||
{{ form_start(form, {'attr': {'style': 'display: none', 'id': 'dashboard-widgets'}}) }}
|
||||
{{ form_end(form) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="grid-stack" id="dashboard-grid-container" style="visibility: hidden">
|
||||
{% for widget in widgets %}
|
||||
{% set gridWidth = widget.width %}
|
||||
{% set gridHeight = widget.height %}
|
||||
{% set widgetWidth = gridWidth * 100 %}
|
||||
{% set widgetHeight = (gridHeight * 72) %}
|
||||
{% if gridHeight == 1 %}
|
||||
{% set widgetHeight = widgetHeight - 67 %}
|
||||
{% elseif gridHeight == 3 %}
|
||||
{% set widgetHeight = widgetHeight %}
|
||||
{% elseif gridHeight == 5 %}
|
||||
{% set widgetHeight = widgetHeight - 10 %}
|
||||
{% elseif gridHeight == 7 %}
|
||||
{% set widgetHeight = widgetHeight - 10 %}
|
||||
{% endif %}
|
||||
<div class="grid-stack-item" data-widget="{{ widget.id }}" gs-w="{{ gridWidth }}" gs-min-w="{{ gridWidth }}" gs-h="{{ gridHeight }}" gs-id="{{ widget.id }}" id="{{ widget.id }}">
|
||||
<div class="grid-stack-item-content">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ widget.title|trans }}</h3>
|
||||
<div class="card-actions">
|
||||
{% if widget.hasForm() %}
|
||||
{{ card_tool_button('configuration', {'title': 'settings', 'translation_domain': 'actions', 'class': 'modal-ajax-form', 'url': path('tasks_create')}) }}
|
||||
{% endif %}
|
||||
{{ card_tool_button('delete', {'title': 'widget_remove', 'translation_domain': 'actions', 'url': '#', 'onclick': "removeWidget('" ~ widget.id ~ "'); return false;"}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-100" preserveAspectRatio="none" width="{{ widgetWidth }}" height="{{ widgetHeight }}" viewBox="0 0 {{ widgetWidth }} {{ widgetHeight }}" stroke="var(--tblr-border-color, #b8cef1)">
|
||||
<line x1="0" y1="0" x2="{{ widgetWidth }}" y2="{{ widgetHeight }}"></line>
|
||||
<line x1="0" y1="{{ widgetHeight }}" x2="{{ widgetWidth }}" y2="0"></line>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
let grid = null;
|
||||
|
||||
function resizeGrid() {
|
||||
let width = document.body.clientWidth;
|
||||
if (width < 576) {
|
||||
grid.column(1);
|
||||
} else if (width < 992) {
|
||||
grid.column(2);
|
||||
} else if (width) {
|
||||
grid.column(4);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
resizeGrid();
|
||||
});
|
||||
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
grid = GridStack.init({
|
||||
'float': false,
|
||||
'cellHeight': 84,
|
||||
'disableResize': true,
|
||||
'column': 4,
|
||||
'marginTop': 0,
|
||||
'marginBottom': 0,
|
||||
'marginLeft': 5,
|
||||
'marginRight': 5,
|
||||
'disableOneColumnMode': true,
|
||||
{% if form is not defined %}
|
||||
'staticGrid': true,
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
resizeGrid();
|
||||
|
||||
document.dispatchEvent(new Event('dashboard.initialized'));
|
||||
document.getElementById('dashboard-grid-container').style.visibility = 'visible';
|
||||
});
|
||||
|
||||
{% if form is defined %}
|
||||
function removeWidget(widgetId)
|
||||
{
|
||||
const widget = document.querySelector('div[data-widget=' + widgetId + ']');
|
||||
if (widget !== null) {
|
||||
grid.removeWidget(widget);
|
||||
}
|
||||
}
|
||||
|
||||
function saveDashboard()
|
||||
{
|
||||
let serializedData = grid.save();
|
||||
const choice = document.getElementById('form_widgets');
|
||||
const values = [];
|
||||
const newChoices = [];
|
||||
|
||||
for (let item of serializedData) {
|
||||
values.push(item.id);
|
||||
for (let i = 0; i < choice.options.length; i++) {
|
||||
if (choice.options[i].value === item.id) {
|
||||
choice.options[i].selected = true;
|
||||
newChoices.push(choice.options[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
choice.options.length = 0;
|
||||
newChoices.forEach(child => choice.appendChild(child));
|
||||
choice.dispatchEvent(new Event('change'));
|
||||
|
||||
document.getElementById('dashboard-widgets').submit();
|
||||
}
|
||||
{% endif %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,10 +1,7 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block page_title %}{{ 'dashboard.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'dashboard.subtitle'|trans }}{% endblock %}
|
||||
|
||||
{% block page_content_class %}{{ parent() }} dashboard{% endblock %}
|
||||
{% block page_class %}dashboard{% endblock %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{{ parent() }}
|
||||
@@ -16,14 +13,25 @@
|
||||
{{ encore_entry_script_tags('chart') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
{% if 'dashboard' in app.request.attributes.get('_route') %}
|
||||
{{ parent() }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if widgets is empty %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{% for row in widgets %}
|
||||
{{ render_widget(row) }}
|
||||
<div class="row row-deck row-cards">
|
||||
{% for widget in widgets %}
|
||||
{% set columnSize = widget.width %}
|
||||
<div class="col-md-{{ columnSize * 3 }}{% if columnSize <= 2 %} col-sm-{{ columnSize * 6 }}{% endif %}" id="{{ widget.id }}">
|
||||
{{ render_widget(widget) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
49
templates/datatable.html.twig
Normal file
49
templates/datatable.html.twig
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.configuration(dataTable) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block table_actions %}
|
||||
{{ tables.actions(dataTable) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block status %}
|
||||
{% if dataTable.getPagination() is not null %}
|
||||
{% from "macros/status.html.twig" import status_count %}
|
||||
{{ status_count(dataTable.getPagination().getNbResults()) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if not dataTable.hasResults() %}
|
||||
{{ widgets.nothing_found(dataTable.getReloadEvents()) }}
|
||||
{% else %}
|
||||
{{ tables.header(dataTable) }}
|
||||
|
||||
{% block datatable_before %}{% endblock %}
|
||||
|
||||
{% set sortedColumns = dataTable.sortedColumnNames %}
|
||||
{% for entry in dataTable %}
|
||||
{% block datatable_row %}
|
||||
<tr{% block datatable_row_attr %}{% endblock %}>
|
||||
{% for column, data in sortedColumns %}
|
||||
{% block datatable_column %}
|
||||
<td class="{{ tables.class(dataTable, column) }}"{% block datatable_column_attr %}{% endblock %}>
|
||||
{% block datatable_column_value %}{% endblock %}
|
||||
</td>
|
||||
{% endblock %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endblock %}
|
||||
{% endfor %}
|
||||
|
||||
{% block datatable_after %}{% endblock %}
|
||||
|
||||
{{ tables.footer(dataTable) }}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -3,28 +3,29 @@
|
||||
{% endif %}
|
||||
{% set _back = back is defined and back is not same as (false) ? back : false %}
|
||||
{% set _reset = reset is defined and reset is same as (false) ? false : true %}
|
||||
<div class="box box-primary">
|
||||
<div class="card mb-3">
|
||||
{% block form_before %}{% endblock %}
|
||||
{{ form_start(form, formStartOptions|default({})) }}
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
{{ title }}
|
||||
{% if form.vars.docu_chapter is defined and form.vars.docu_chapter is not empty %}
|
||||
<a href="{{ form.vars.docu_chapter|docu_link }}" target="_blank"><i class="{{ 'help'|icon }}"></i></a>
|
||||
<a class="form-help" href="{{ form.vars.docu_chapter|docu_link }}" target="_blank">?</a>
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="card-body">
|
||||
{{ form_errors(form) }}
|
||||
{% block form_body %}
|
||||
{{ form_widget(form) }}
|
||||
{{ form_rest(form) }}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<div class="card-footer">
|
||||
{% block submit_button %}
|
||||
<input type="submit" data-loading-text="{{ (submit_button|default('action.save'))|trans }}…" value="{{ (submit_button|default('action.save'))|trans }}" class="btn btn-primary" />
|
||||
{% endblock %}
|
||||
{% if _back is not same as (false) %}
|
||||
<a href="{{ _back }}" class="btn btn-link">{{ 'action.back'|trans }}</a>
|
||||
<a href="{{ _back }}" class="btn btn-link">{{ 'back'|trans({}, 'actions') }}</a>
|
||||
{% endif %}
|
||||
{% if _reset is not same as (false) %}
|
||||
<input type="reset" value="{{ 'action.reset'|trans }}" class="btn btn-link pull-right" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'boxtype': 'danger'} %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {'boxtype': 'danger'} %}
|
||||
{% block box_before %}
|
||||
{{ form_start(form) }}
|
||||
{% endblock %}
|
||||
@@ -9,7 +9,7 @@
|
||||
{% block form_body %}
|
||||
{% if used is same as (false) %}
|
||||
{{ 'delete.not_in_use'|trans }}
|
||||
<div class="hidden">
|
||||
<div class="d-none">
|
||||
{{ form_widget(form) }}
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -19,9 +19,9 @@
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
{% block box_footer %}
|
||||
<input type="submit" value="{{ 'action.delete'|trans }}" class="btn btn-danger" />
|
||||
<input type="submit" value="{{ 'delete'|trans }}" class="btn btn-danger" />
|
||||
{% if back %}
|
||||
<a href="{{ back }}" class="btn btn-link">{{ 'action.back'|trans }}</a>
|
||||
<a href="{{ back }}" class="btn btn-link">{{ 'back'|trans({}, 'actions') }}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_after %}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{% embed 'embeds/modal.html.twig' %}
|
||||
{% embed '@theme/embeds/modal.html.twig' %}
|
||||
{% set used = used|default(false) %}
|
||||
{% block modal_id %}form_modal{% endblock %}
|
||||
{% block modal_class %}{% if used %}modal-danger{% endif %}{% endblock %}
|
||||
{% block modal_before %}{{ form_start(form) }}{% endblock %}
|
||||
{% block modal_title %}
|
||||
{{ title|default('confirm.delete'|trans) }}
|
||||
@@ -9,7 +8,7 @@
|
||||
{% block modal_body %}
|
||||
{% if used is same as (false) %}
|
||||
{{ 'delete.not_in_use'|trans }}
|
||||
<div class="hidden">
|
||||
<div class="d-none">
|
||||
{{ form_widget(form) }}
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -18,8 +17,8 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block modal_footer %}
|
||||
<button type="submit" class="btn {% if used %}btn-outline{% else %}btn-primary{% endif %} pull-left modal-form-save" data-loading-text="{{ 'action.delete'|trans }}..." id="{{ block('modal_id') }}_save">{{ 'action.delete'|trans }}</button>
|
||||
<button type="button" class="btn btn-default btn-cancel" data-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||
<button type="submit" class="btn {% if used %}btn-danger{% else %}btn-primary{% endif %} pull-left modal-form-save" data-loading-text="{{ 'delete'|trans }}..." id="{{ block('modal_id') }}_save">{{ 'delete'|trans }}</button>
|
||||
<button type="button" class="btn btn-cancel" data-bs-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||
{% endblock %}
|
||||
{% block modal_end %}{{ form_end(form) }}{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
{#
|
||||
REQUIRED CHANGES NEED TO BE DONE IN THESE FILES:
|
||||
- embeds/modal.html.twig
|
||||
- default/_form_modal.html.twig
|
||||
#}
|
||||
<div class="modal {% block modal_class %}{% endblock %}" id="{% block modal_id %}form_modal{% endblock %}" tabindex="-1" role="dialog" aria-labelledby="{{ block('modal_id') }}_label">
|
||||
<div class="modal modal-blur fade {% block modal_class %}{% endblock %}" id="{% block modal_id %}form_modal{% endblock %}" tabindex="-1" role="dialog" aria-labelledby="{{ block('modal_id') }}_label">
|
||||
<div class="modal-dialog {% block modal_size %}modal-lg{% endblock %}" role="document">
|
||||
<div class="modal-content">
|
||||
{% block form_before %}{% endblock %}
|
||||
{% block modal_before %}{{ form_start(form, formStartOptions|default({})) }}{% endblock %}
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="{{ 'action.close'|trans }}"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="{{ block('modal_id') }}_label">
|
||||
<h5 class="modal-title" id="{{ block('modal_id') }}_label">
|
||||
{% block modal_title %}
|
||||
{{ title }}
|
||||
{% if form.vars.docu_chapter is defined and form.vars.docu_chapter is not empty %}
|
||||
<a href="{{ form.vars.docu_chapter|docu_link }}" target="_blank"><i class="{{ 'help'|icon }}"></i></a>
|
||||
<a class="form-help" href="{{ form.vars.docu_chapter|docu_link }}" target="_blank">?</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</h4>
|
||||
</h5>
|
||||
<button type="button" class="btn-close{% if tabler_bundle.isDarkMode() %} btn-close-white{% endif %}" data-bs-dismiss="modal" aria-label="{{ 'action.close'|trans }}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% block modal_body %}
|
||||
{{ form_errors(form) }}
|
||||
{% block form_body %}
|
||||
{{ form_widget(form) }}
|
||||
{{ form_rest(form) }}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{% block modal_footer %}
|
||||
{% block submit_button %}
|
||||
<button type="submit" class="btn btn-primary pull-left modal-form-save" data-loading-text="{{ (submit_button|default('action.save'))|trans }}…" id="{{ block('modal_id') }}_save">{{ (submit_button|default('action.save'))|trans }}</button>
|
||||
<button type="submit" class="btn btn-primary pull-left modal-form-save" data-loading-text="{{ (submit_button|default('action.save'))|trans }}…" id="{{ block('modal_id') }}_save" data-hotkey="ctrl+Enter">{{ (submit_button|default('action.save'))|trans }}</button>
|
||||
{% endblock %}
|
||||
<button type="button" class="btn btn-default btn-cancel" data-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||
<button type="button" class="btn btn-cancel" data-bs-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% block modal_end %}{{ form_end(form) }}{% endblock %}
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block page_title %}{{ 'menu.doctor'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'label.version'|trans }}: {{ constant('App\\Constants::VERSION') }} {{ constant('App\\Constants::STATUS') }} ({{ environment }}){% endblock %}
|
||||
{% block page_actions %}
|
||||
{% set event = actions(app.user, 'doctor', 'index') %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% block status %}
|
||||
{% from "macros/status.html.twig" import status %}
|
||||
{{ status(constant('App\\Constants::VERSION')) }}
|
||||
{{ status(environment, environment == 'dev' ? 'red' : 'green') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if release is not null and release is iterable %}
|
||||
{% embed '@theme/embeds/card.html.twig' with release %}
|
||||
{% block box_title %}Kimai {{ release.version }} available (released on {{ release.date|date_short }}){% endblock %}
|
||||
{% block box_body %}
|
||||
{{ release.content|md2html }}
|
||||
<p>
|
||||
<a class="btn btn-primary" href="{{ release.url }}" target=_blank">Read more</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endif %}
|
||||
|
||||
{% set permissionOpts = {collapsed: true, boxtype: 'success'} %}
|
||||
{% for name, value in directories %}
|
||||
{% if value is same as (false) %}
|
||||
@@ -17,11 +27,11 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with permissionOpts %}
|
||||
{% embed '@theme/embeds/card.html.twig' with permissionOpts %}
|
||||
{% block box_title %}File permissions{% endblock %}
|
||||
{% block box_body %}
|
||||
<p>
|
||||
If you see a warning icon <i class="{{ 'warning'|icon }} text-red"></i> in the list below, you should talk to your administrator.
|
||||
If you see a warning icon <i class="{{ 'warning'|icon(false) }} text-red"></i> in the list below, you should talk to your administrator.
|
||||
The path is either not readable or not writable and Kimai might not work as expected.
|
||||
</p>
|
||||
<table class="table">
|
||||
@@ -30,9 +40,9 @@
|
||||
<th style="width:15%">{{ name }}</th>
|
||||
<td>
|
||||
{% if value is same as (false) %}
|
||||
<i class="{{ 'warning'|icon }} text-red"></i>
|
||||
<i class="{{ 'warning'|icon(false) }} text-red"></i>
|
||||
{% else %}
|
||||
<i class="{{ 'success'|icon }} text-green"></i>
|
||||
<i class="{{ 'success'|icon(false) }} text-green"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -48,12 +58,11 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with extensionOpts %}
|
||||
{% block box_title %}Required PHP extensions{% endblock %}
|
||||
{% embed '@theme/embeds/card.html.twig' with extensionOpts %}
|
||||
{% block box_title %}PHP extensions{% endblock %}
|
||||
{% block box_body %}
|
||||
<p>
|
||||
If you see a warning icon <i class="{{ 'warning'|icon }} text-red"></i> in the list below, you should talk to your administrator.
|
||||
There is a missing PHP extension and Kimai might not work as expected.
|
||||
If you see a warning icon <i class="{{ 'warning'|icon(false) }} text-red"></i> in the list below, you should talk to your administrator, as there is a missing PHP extension causing Kimai not to work as expected.
|
||||
</p>
|
||||
<table class="table">
|
||||
{% for name, value in extensions %}
|
||||
@@ -61,9 +70,9 @@
|
||||
<th style="width:15%">{{ name }}</th>
|
||||
<td>
|
||||
{% if value is same as (false) %}
|
||||
<i class="{{ 'warning'|icon }} text-red"></i>
|
||||
<i class="{{ 'warning'|icon(false) }} text-red"></i>
|
||||
{% else %}
|
||||
<i class="{{ 'success'|icon }} text-green"></i>
|
||||
<i class="{{ 'success'|icon(false) }} text-green"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -76,11 +85,12 @@
|
||||
{% if logSize > 52428800 %}
|
||||
{% set logOpts = {boxtype: 'danger'} %}
|
||||
{% endif %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with logOpts %}
|
||||
{% embed '@theme/embeds/card.html.twig' with logOpts %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}Logfile (max. {{ logLines }} last lines){% endblock %}
|
||||
{% block box_tools %}
|
||||
{% if log_delete %}
|
||||
<a class="btn-box-tool confirmation-link" href="{{ path('doctor_flush_log', {'token': csrf_token('doctor.flush_log')}) }}" data-question="confirm.delete"><i class="{{ 'delete'|icon }}"></i></a>
|
||||
{{ widgets.card_tool_button('delete', {'class': 'confirmation-link', 'title': 'delete', 'url': path('doctor_flush_log', {'token': csrf_token('doctor.flush_log')}), 'attr': {'data-question': 'confirm.delete'}}) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body %}
|
||||
@@ -103,38 +113,43 @@
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {collapsed: true} %}
|
||||
{% block box_title %}Composer packages{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {collapsed: true} %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}Composer{% endblock %}
|
||||
{% block box_body_class %}{% if composer|length > 0 %}p-0{% endif %}{% endblock %}
|
||||
{% block box_body %}
|
||||
<table class="table table-hover">
|
||||
{% for name, value in composer %}
|
||||
<tr>
|
||||
<th style="width:15%">{{ name }}</th>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if composer|length == 0 %}
|
||||
{{ widgets.alert('warning', 'Could not detect installed composer packages, please upgrade your Composer installation to version 2.x.') }}
|
||||
{% else %}
|
||||
<table class="table table-hover">
|
||||
{% for name, value in composer %}
|
||||
<tr>
|
||||
<th class="text-nowrap w-min">{{ name }}</th>
|
||||
<td class="text-break">{{ value }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {collapsed: true} %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {collapsed: true} %}
|
||||
{% block box_title %}PHP{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_body %}
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<th style="width:15%">Version</th>
|
||||
<th class="text-nowrap w-min">{{ 'version'|trans }}</th>
|
||||
<td>{{ constant('PHP_VERSION') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="width:15%;max-width:25%">Modules</th>
|
||||
<td>{{ modules|join(', ') }}</td>
|
||||
<th class="text-nowrap w-min">Modules</th>
|
||||
<td class="text-break">{{ modules|join(', ') }}</td>
|
||||
</tr>
|
||||
{% for name, value in settings %}
|
||||
<tr>
|
||||
<th style="width:15%">{{ name }}</th>
|
||||
<td>
|
||||
<th class="text-nowrap w-min">{{ name }}</th>
|
||||
<td class="text-break">
|
||||
{% if value is empty %}
|
||||
<i>unknown</i>
|
||||
{% else %}
|
||||
@@ -143,19 +158,10 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {collapsed: true} %}
|
||||
{% block box_title %}Server{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body %}
|
||||
<table class="table table-hover">
|
||||
{% for name, value in info %}
|
||||
<tr>
|
||||
<th style="width:15%">{{ name }}</th>
|
||||
<td>{{ value|raw }}</td>
|
||||
<th class="text-nowrap w-min">{{ name }}</th>
|
||||
<td class="text-break">{{ value|raw }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
{% set timeBudgetVisible = is_granted('time', entity) %}
|
||||
{% set durationTrans = entity.isMonthlyBudget() ? 'stats.durationMonth' : 'stats.durationTotal' %}
|
||||
{% if timeBudgetVisible %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}
|
||||
{{ 'label.timeBudget'|trans }} {% if entity.isMonthlyBudget() %}({{ 'label.budgetType_month'|trans }}){% endif %}
|
||||
{{ 'timeBudget'|trans }} {% if entity.isMonthlyBudget() %}({{ 'budgetType_month'|trans }}){% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_attributes %}id="time_budget_box"{% endblock %}
|
||||
{% block box_body %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-5">
|
||||
<div class="col-12 col-md-6 order-1 order-md-0">
|
||||
<table class="table table-hover dataTable">
|
||||
<tr>
|
||||
<td>{{ 'label.timeBudget'|trans }}</td>
|
||||
<td class="text-nowrap text-right">
|
||||
<td>{{ 'timeBudget'|trans }}</td>
|
||||
<td class="text-nowrap text-end">
|
||||
{% if entity.timeBudget > 0 %}
|
||||
{{ entity.timeBudget|duration }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-nowrap text-right">
|
||||
<td class="text-nowrap text-end">
|
||||
{% if entity.timeBudget > 0 %}
|
||||
100%
|
||||
{% else %}
|
||||
@@ -36,21 +36,21 @@
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ durationTrans|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.duration|duration }}</td>
|
||||
<td class="text-nowrap text-right">{{ totalPercentReached|number_format(2) }}%</td>
|
||||
<td class="text-nowrap text-end">{{ stats.duration|duration }}</td>
|
||||
<td class="text-nowrap text-end">{{ totalPercentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% set percentReached = 0 %}
|
||||
{% if stats.duration > 0 %}
|
||||
{% set percentReached = (stats.durationBillable / (stats.duration / 100)) %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ 'label.billable'|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.durationBillable|duration }}</td>
|
||||
<td class="text-nowrap text-right">{{ percentReached|number_format(2) }}%</td>
|
||||
<td>{{ 'billable'|trans }}</td>
|
||||
<td class="text-nowrap text-end">{{ stats.durationBillable|duration }}</td>
|
||||
<td class="text-nowrap text-end">{{ percentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% if entity.timeBudget > 0 %}
|
||||
<tr>
|
||||
<th colspan="3" class="text-nowrap text-right">
|
||||
<th colspan="3" class="text-nowrap text-end">
|
||||
{% if totalPercentReached < 100 %}
|
||||
{{ 'stats.percentUsedLeft'|trans({'%percent%': totalPercentReached|number_format(2), '%left%': (entity.timeBudget - stats.durationBillable)|duration}) }}
|
||||
{% else %}
|
||||
@@ -61,42 +61,40 @@
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-7">
|
||||
<div class="box-padding">
|
||||
{% if entity.timeBudget > 0 %}
|
||||
{{ progress.progressbar(entity.timeBudget, stats.durationBillable, durationTrans|trans, stats.durationBillable|duration ~ ' / ' ~ entity.timeBudget|duration) }}
|
||||
{% endif %}
|
||||
{{ progress.progressbar(stats.duration, stats.durationBillable, 'label.billable'|trans, stats.durationBillable|duration ~ ' / ' ~ stats.duration|duration, true) }}
|
||||
</div>
|
||||
<div class="col-12 col-md-6 order-0 order-md-1 p-3">
|
||||
{% if entity.timeBudget > 0 %}
|
||||
<div class="mb-3">{{ progress.progressbar(entity.timeBudget, stats.durationBillable, durationTrans|trans, stats.durationBillable|duration ~ ' / ' ~ entity.timeBudget|duration) }}</div>
|
||||
{% endif %}
|
||||
<div class="mb-3">{{ progress.progressbar(stats.duration, stats.durationBillable, 'billable'|trans, stats.durationBillable|duration ~ ' / ' ~ stats.duration|duration, true) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget', entity) %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% import "macros/progressbar.html.twig" as progress %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}
|
||||
{{ 'label.budget'|trans }} {% if entity.isMonthlyBudget() %}({{ 'label.budgetType_month'|trans }}){% endif %}
|
||||
{{ 'budget'|trans }} {% if entity.isMonthlyBudget() %}({{ 'budgetType_month'|trans }}){% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_attributes %}id="budget_box"{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set rateTrans = entity.isMonthlyBudget() ? 'stats.amountMonth' : 'stats.amountTotal' %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-5">
|
||||
<div class="col-12 col-md-6 order-1 order-md-0">
|
||||
<table class="table table-hover dataTable">
|
||||
<tr>
|
||||
<td>{{ 'label.budget'|trans }}</td>
|
||||
<td class="text-nowrap text-right">
|
||||
<td>{{ 'budget'|trans }}</td>
|
||||
<td class="text-nowrap text-end">
|
||||
{% if entity.budget > 0 %}
|
||||
{{ entity.budget|money(currency) }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-nowrap text-right">
|
||||
<td class="text-nowrap text-end">
|
||||
{% if entity.budget > 0 %}
|
||||
100%
|
||||
{% else %}
|
||||
@@ -110,38 +108,38 @@
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ rateTrans|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.rate|money(currency) }}</td>
|
||||
<td class="text-nowrap text-right">{{ totalPercentReached|number_format(2) }}%</td>
|
||||
<td class="text-nowrap text-end">{{ stats.rate|money(currency) }}</td>
|
||||
<td class="text-nowrap text-end">{{ totalPercentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% set percentReached = 0 %}
|
||||
{% if stats.rate > 0 %}
|
||||
{% set percentReached = (stats.rateBillable / (stats.rate / 100)) %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ 'label.billable'|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.rateBillable|money(currency) }}</td>
|
||||
<td class="text-nowrap text-right">{{ percentReached|number_format(2) }}%</td>
|
||||
<td>{{ 'billable'|trans }}</td>
|
||||
<td class="text-nowrap text-end">{{ stats.rateBillable|money(currency) }}</td>
|
||||
<td class="text-nowrap text-end">{{ percentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% set percentReached = 0 %}
|
||||
{% if stats.rateBillable > 0 %}
|
||||
{% set percentReached = (stats.internalRate / (stats.rateBillable / 100)) %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ 'label.rate_internal'|trans }}</td>
|
||||
<td class="text-nowrap text-right">{{ stats.internalRate|money(currency) }}</td>
|
||||
<td class="text-nowrap text-right">{{ percentReached|number_format(2) }}%</td>
|
||||
<td>{{ 'internalRate'|trans }}</td>
|
||||
<td class="text-nowrap text-end">{{ stats.internalRate|money(currency) }}</td>
|
||||
<td class="text-nowrap text-end">{{ percentReached|number_format(2) }}%</td>
|
||||
</tr>
|
||||
{% if not entity.isMonthlyBudget() and timeBudgetVisible and stats.duration > 0 %}
|
||||
{% set realHourlyRate = stats.rateBillable / stats.duration|chart_duration %}
|
||||
<tr>
|
||||
<td>{{ 'label.hourlyRate'|trans }} ({{ 'label.billable'|trans }} / {{ durationTrans|trans }})</td>
|
||||
<td class="text-nowrap text-right">{{ realHourlyRate|money(currency) }}</td>
|
||||
<td class="text-nowrap text-right">-</td>
|
||||
<td>{{ 'hourlyRate'|trans }} ({{ 'billable'|trans }} / {{ durationTrans|trans }})</td>
|
||||
<td class="text-nowrap text-end">{{ realHourlyRate|money(currency) }}</td>
|
||||
<td class="text-nowrap text-end">-</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if entity.budget > 0 %}
|
||||
<tr>
|
||||
<th colspan="3" class="text-nowrap text-right">
|
||||
<tr>
|
||||
<th colspan="3" class="text-nowrap text-end">
|
||||
{% if totalPercentReached < 100 %}
|
||||
{{ 'stats.percentUsedLeft'|trans({'%percent%': totalPercentReached|number_format(2), '%left%': (entity.budget - stats.rateBillable)|money(currency)}) }}
|
||||
{% else %}
|
||||
@@ -152,14 +150,12 @@
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-7">
|
||||
<div class="box-padding">
|
||||
{% if entity.budget > 0 %}
|
||||
{{ progress.progressbar(entity.budget, stats.rateBillable, rateTrans|trans, stats.rateBillable|money(currency) ~ ' / ' ~ entity.budget|money(currency)) }}
|
||||
{% endif %}
|
||||
{{ progress.progressbar(stats.rate, stats.rateBillable, 'label.billable'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ stats.rate|money(currency), true) }}
|
||||
{{ progress.progressbar(stats.rateBillable, stats.internalRate, 'label.rate_internal'|trans, stats.internalRate|money(currency) ~ ' / ' ~ stats.rateBillable|money(currency)) }}
|
||||
</div>
|
||||
<div class="col-12 col-md-6 order-0 order-md-1 p-3">
|
||||
{% if entity.budget > 0 %}
|
||||
<div class="mb-3">{{ progress.progressbar(entity.budget, stats.rateBillable, rateTrans|trans, stats.rateBillable|money(currency) ~ ' / ' ~ entity.budget|money(currency)) }}</div>
|
||||
{% endif %}
|
||||
<div class="mb-3">{{ progress.progressbar(stats.rate, stats.rateBillable, 'billable'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ stats.rate|money(currency), true) }}</div>
|
||||
<div class="mb-3">{{ progress.progressbar(stats.rateBillable, stats.internalRate, 'internalRate'|trans, stats.internalRate|money(currency) ~ ' / ' ~ stats.rateBillable|money(currency)) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'form': form, 'comments': comments, 'route_pin': route_pin|default(null), 'route_delete': route_delete|default(null), 'delete_by_user': delete_by_user|default(false), 'csrf_delete': csrf_token(csrf_delete|default('comment.delete')), 'csrf_pin': csrf_token(csrf_pin|default('comment.pin'))} %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {'form': form, 'comments': comments, 'route_pin': route_pin|default(null), 'route_delete': route_delete|default(null), 'delete_by_user': delete_by_user|default(false), 'csrf_delete': csrf_token(csrf_delete|default('comment.delete')), 'csrf_pin': csrf_token(csrf_pin|default('comment.pin'))} %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'label.comment'|trans }}{% endblock %}
|
||||
{% block box_title %}{{ 'comment'|trans }}{% endblock %}
|
||||
{% block box_attributes %}id="comments_box"{% endblock %}
|
||||
{% block box_body_class %}box-body-scrollable{% endblock %}
|
||||
{% block box_body_class %}box-body-scrollable{% if comments|length > 0%} p-0{% endif %}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set replacer = {} %}
|
||||
{% for pref in app.user.preferences %}
|
||||
@@ -11,54 +11,48 @@
|
||||
{% if comments|length == 0 %}
|
||||
{{ 'error.no_comments_found'|trans }}
|
||||
{% else %}
|
||||
{% for comment in comments %}
|
||||
<div class="direct-chat-msg">
|
||||
<div class="direct-chat-info clearfix">
|
||||
<span class="direct-chat-name pull-left">
|
||||
{{ widgets.username(comment.createdBy) }}
|
||||
</span>
|
||||
<span class="direct-chat-timestamp pull-left">
|
||||
|
||||
{{ comment.createdAt|date_full }}
|
||||
|
||||
</span>
|
||||
<span class="pull-right">
|
||||
{% if route_pin is not null %}
|
||||
<a href="{{ path(route_pin, {'id': comment.id, 'token': csrf_pin}) }}" class="btn btn-default btn-xs {% if comment.pinned %}active{% endif %}"><i class="{{ 'pin'|icon }}"></i></a>
|
||||
{% elseif comment.pinned %}
|
||||
<i class="{{ 'pin'|icon }}"></i>
|
||||
{% endif %}
|
||||
{% if route_delete is not null and ((not delete_by_user) or (delete_by_user and comment.createdBy.id == app.user.id)) %}
|
||||
<a href="{{ path(route_delete, {'id': comment.id, 'token': csrf_delete}) }}" class="confirmation-link btn btn-default btn-xs" data-question="confirm.delete"><i class="{{ 'delete'|icon }}"></i></a>
|
||||
{% endif %}
|
||||
</span>
|
||||
<div class="divide-y">
|
||||
{% for comment in comments %}
|
||||
<div class="p-3">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
{{ widgets.user_avatar(comment.createdBy, false) }}
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="text-truncate">
|
||||
<strong>{{ widgets.username(comment.createdBy) }}</strong>
|
||||
</div>
|
||||
<div class="text-muted">{{ comment.createdAt|date_time }}</div>
|
||||
</div>
|
||||
<div class="col-auto align-self-center">
|
||||
{% if route_pin is not null %}
|
||||
<a href="{{ path(route_pin, {'id': comment.id, 'token': csrf_pin}) }}" class="btn{% if not comment.pinned %} btn-ghost-secondary{% else %} btn-ghost-info{% endif%} btn-icon {% if comment.pinned %}active{% endif %} pin-comment-link">{{ icon('pin') }}</a>
|
||||
{% elseif comment.pinned %}
|
||||
{{ icon('pin') }}
|
||||
{% endif %}
|
||||
{% if route_delete is not null and ((not delete_by_user) or (delete_by_user and comment.createdBy.id == app.user.id)) %}
|
||||
<a href="{{ path(route_delete, {'id': comment.id, 'token': csrf_delete}) }}" class="confirmation-link btn btn-ghost-secondary btn-icon delete-comment-link" data-question="confirm.delete">{{ icon('delete') }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{{ widgets.user_avatar(comment.createdBy, false, 'direct-chat-img img-sm') }}
|
||||
<div class="direct-chat-text">
|
||||
<div class="row pt-2 direct-chat-text">
|
||||
{{ comment.message|replace(replacer)|comment2html }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_footer -%}
|
||||
{% if form is not null %}
|
||||
{% block box_footer %}
|
||||
{%- if form is not null -%}
|
||||
{{ form_start(form) }}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{{ form_widget(form.message, {'attr': {'rows': '3', 'placeholder': 'placeholder.type_message'|trans, 'style': 'margin-bottom: 5px'}}) }}
|
||||
<button type="submit" class="btn btn-default">
|
||||
<i class="{{ 'comment'|icon }}"></i>
|
||||
{{ 'label.comment'|trans }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
</div>
|
||||
</div>
|
||||
{{ form_widget(form.message, {'attr': {'rows': '3', 'placeholder': 'placeholder.type_message'|trans, 'style': 'margin-bottom: 5px'}}) }}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ icon('comment', true) }}
|
||||
{{ 'comment'|trans }}
|
||||
</button>
|
||||
{{ form_widget(form) }}
|
||||
{{ form_end(form) }}
|
||||
{% endif %}
|
||||
{%- endblock %}
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{#
|
||||
REQUIRED CHANGES NEED TO BE DONE IN THESE FILES:
|
||||
- embeds/modal.html.twig
|
||||
- default/_form_modal.html.twig
|
||||
#}
|
||||
<div class="modal{% if block('modal_class') is defined %} {{ block('modal_class') }}{% endif %}" id="{{ block('modal_id') }}" tabindex="-1" role="dialog" aria-labelledby="{{ block('modal_id') }}_label">
|
||||
<div class="modal-dialog {% block modal_size %}modal-lg{% endblock %}" role="document">
|
||||
<div class="modal-content">
|
||||
{% if block('modal_before') is defined %}{{ block('modal_before') }}{% endif %}
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="{{ 'action.close'|trans }}"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="{{ block('modal_id') }}_label">{{ block('modal_title') }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ block('modal_body') }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{ block('modal_footer') }}
|
||||
</div>
|
||||
{% if block('modal_end') is defined %}{{ block('modal_end') }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,13 +1,13 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'id': id, 'entity': entity, 'create_url': create_url, 'delete_route': delete_route, 'currency': currency} %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {'id': id, 'entity': entity, 'create_url': create_url, 'delete_route': delete_route, 'currency': currency} %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_attributes %}id="{{ id }}"{% endblock %}
|
||||
{% block box_title %}
|
||||
{{ 'label.hourlyRate'|trans }}
|
||||
{{ 'rates.title'|trans }}
|
||||
{% endblock %}
|
||||
{% block box_tools %}
|
||||
<a class="modal-ajax-form open-edit btn btn-default btn-sm" data-href="{{ create_url }}" data-toggle="tooltip" data-placement="top" title="{{ 'create'|trans }}"><i class="{{ 'create'|icon }}"></i></a>
|
||||
{{ widgets.card_tool_button('create', {'class': 'modal-ajax-form open-edit', 'title': 'create', 'url': create_url}) }}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body_class %}{% if rates is not empty %}p-0{% endif %}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if rates is empty %}
|
||||
<div class="comment">{{ 'rates.empty'|trans }}</div>
|
||||
@@ -16,23 +16,23 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{{ 'label.user'|trans }}
|
||||
{{ 'user'|trans }}
|
||||
</th>
|
||||
<th>
|
||||
{{ 'label.hourlyRate'|trans }}
|
||||
{{ 'hourlyRate'|trans }}
|
||||
</th>
|
||||
<th class="d-none d-sm-table-cell">
|
||||
{{ 'internalRate'|trans }}
|
||||
</th>
|
||||
<th>
|
||||
{{ 'label.rate_internal'|trans }}
|
||||
</th>
|
||||
<th>
|
||||
{{ 'label.fixedRate'|trans }}
|
||||
{{ 'fixedRate'|trans }}
|
||||
</th>
|
||||
<th class="actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for rate in rates %}
|
||||
<tr>
|
||||
<tr class="modal-ajax-form open-edit" data-href="{{ path(edit_route, {'id': entity.id, 'rate': rate.id}) }}">
|
||||
<td>
|
||||
{% if rate.user is not null %}
|
||||
{{ widgets.user_avatar(rate.user) }}
|
||||
@@ -43,14 +43,17 @@
|
||||
<td>
|
||||
{{ rate.rate|money(currency) }}
|
||||
</td>
|
||||
<td>
|
||||
<td class="d-none d-sm-table-cell">
|
||||
{{ rate.internalRate|money(currency) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ widgets.label_boolean(rate.fixed) }}
|
||||
</td>
|
||||
<td class="actions">
|
||||
<a href="{{ path(delete_route, {'id': entity.id, 'rateId': rate.id}) }}" class="btn btn-default btn-xs api-link" data-question="confirm.delete" data-event="kimai.rateUpdate kimai.rateDelete" data-method="DELETE" data-msg-error="action.delete.error" data-msg-success="action.delete.success"><i class="{{ 'delete'|icon }}"></i></a>
|
||||
<div class="btn-group">
|
||||
<a href="{{ path(edit_route, {'id': entity.id, 'rate': rate.id}) }}" class="modal-ajax-form btn btn-icon">{{ icon('edit', true) }}</a>
|
||||
<a href="{{ path(delete_route, {'id': entity.id, 'rateId': rate.id}) }}" class="btn btn-icon api-link" data-question="confirm.delete" data-event="kimai.rateUpdate kimai.rateDelete" data-method="DELETE" data-msg-error="action.delete.error" data-msg-success="action.delete.success">{{ icon('delete', true) }}</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -7,25 +7,25 @@
|
||||
'empty_message': empty_message|default('team.visibility_global')
|
||||
}
|
||||
%}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with options %}
|
||||
{% embed '@theme/embeds/card.html.twig' with options %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_tools %}
|
||||
{% if route_create is not null and (teams|length == 0 or team is null) and is_granted('create_team') %}
|
||||
<a class="btn btn-default btn-sm" href="{{ route_create }}" data-toggle="tooltip" data-placement="top" title="{{ 'team.create_default'|trans({}, 'teams') }}"><i class="{{ 'create'|icon }}"></i></a>
|
||||
{{ widgets.card_tool_button('create', {'title': 'team.create_default', 'translation_domain': 'teams', 'url': route_create}) }}
|
||||
{% endif %}
|
||||
{% if route_edit is not null %}
|
||||
<a class="modal-ajax-form open-edit btn btn-default btn-sm" href="{{ route_edit }}" data-toggle="tooltip" data-placement="top" title="{{ 'action.edit'|trans }}"><i class="{{ 'edit'|icon }}"></i></a>
|
||||
{{ widgets.card_tool_button('edit', {'class': 'modal-ajax-form open-edit', 'title': 'action.edit', 'url': route_edit}) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_attributes %}id="team_listing_box"{% endblock %}
|
||||
{% block box_title %}
|
||||
{{ 'permissions'|trans({}, 'actions') }}
|
||||
{% if teams|length > 0 %}<small class="hidden-xs">{{ 'team.visibility_restricted'|trans({}, 'teams') }}</small>{% endif %}
|
||||
{% if teams|length > 0 %}<small class="text-muted d-none d-sm-inline ms-1">{{ 'team.visibility_restricted'|trans({}, 'teams') }}</small>{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body_class %}{% if teams|length > 0 %}p-0{% endif %}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if teams|length > 0 %}
|
||||
{{ widgets.team_list(teams) }}
|
||||
{{ widgets.team_list(teams, {name_class: 'w-min'}) }}
|
||||
{% else %}
|
||||
<div class="comment">{{ empty_message|default('team.visibility_global')|trans({}, 'teams') }}</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
{% extends '@AdminLTE/layout/security-layout.html.twig' %}
|
||||
|
||||
{% set errorTitleKey = "http_error_#{status_code}.description" %}
|
||||
{% set errorTitleTrans = errorTitleKey|trans({ '%status_code%': status_code }, 'exceptions') %}
|
||||
{% if errorTitleKey == errorTitleTrans %}
|
||||
{% set errorTitleKey = 'http_error.description' %}
|
||||
{% set errorTitleTrans = errorTitleKey|trans({ '%status_code%': status_code }, 'exceptions') %}
|
||||
{% endif %}
|
||||
|
||||
{% set errorMessageKey = "http_error_#{status_code}.suggestion" %}
|
||||
{% set errorMessageTrans = errorMessageKey|trans({ '%status_code%': status_code }, 'exceptions') %}
|
||||
{% if errorMessageKey == errorMessageTrans %}
|
||||
{% set errorMessageKey = 'http_error.suggestion' %}
|
||||
{% set errorMessageTrans = errorMessageKey|trans({ '%status_code%': status_code }, 'exceptions') %}
|
||||
{% endif %}
|
||||
|
||||
{% block logo_login %}{% include 'partials/logo_login.html.twig' %}{% endblock %}
|
||||
{% block title %}{{ status_code }} - {{ 'http_error.title'|trans({}, 'exceptions') }}{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
{% include 'partials/head.html.twig' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{{ encore_entry_link_tags('app') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ encore_entry_script_tags('app') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block login_box_icon %}{% block error_icon %}{% endblock %}{% endblock %}
|
||||
|
||||
{% block login_box_msg %}
|
||||
{{ errorTitleTrans }}
|
||||
{% endblock %}
|
||||
|
||||
{% block login_form %}
|
||||
{% block error_content %}
|
||||
<p>
|
||||
{{ errorMessageTrans }}
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn btn-primary" href="{{ path('homepage') }}">{{ 'dashboard.title'|trans }}</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block login_social_auth %}{% endblock %}
|
||||
{% block login_actions %}{% endblock %}
|
||||
@@ -1,28 +1,50 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
|
||||
{% set totalDuration = 0 %}
|
||||
{% set totalRates = {} %}
|
||||
|
||||
{% if too_many is not same as (true) and preview_show and entries is not empty %}
|
||||
{% for row in by_customer %}
|
||||
{% set totalDuration = totalDuration + row.duration %}
|
||||
{% set customerCurrency = row.customer.currency %}
|
||||
{% if totalRates[customerCurrency] is not defined %}
|
||||
{% set totalRates = totalRates|merge({(customerCurrency): 0.00}) %}
|
||||
{% endif %}
|
||||
{% set totalRates = totalRates|merge({(customerCurrency): totalRates[customerCurrency] + row.rate}) %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% set columns = {
|
||||
'avatar': {'class': 'text-nowrap w-avatar d-none d-md-table-cell', 'title': false, 'orderBy': false},
|
||||
'date': {'class': 'alwaysVisible text-nowrap', 'orderBy': false},
|
||||
'user': {'class': 'hidden-xs hidden-sm', 'orderBy': false},
|
||||
'project': {'class': 'hidden-xs hidden-sm', 'orderBy': false},
|
||||
'activity': {'class': 'hidden-xs hidden-sm hidden-md', 'orderBy': false},
|
||||
'description': {'class': 'hidden-xs hidden-sm hidden-md timesheet-description', 'orderBy': false},
|
||||
'unit_price': {'class': 'text-right hidden hidden-xs text-nowrap', 'orderBy': false},
|
||||
'duration': {'class': 'text-right text-nowrap', 'orderBy': false},
|
||||
'rate_internal': {'class': 'text-right hidden text-nowrap', 'orderBy': false},
|
||||
'total_rate': {'class': 'text-right text-nowrap', 'orderBy': false},
|
||||
'exported': {'class': 'alwaysVisible', 'orderBy': false},
|
||||
'user': {'class': 'd-none', 'orderBy': false},
|
||||
'project': {'class': 'd-none d-sm-table-cell', 'orderBy': false},
|
||||
'activity': {'class': 'd-none', 'orderBy': false},
|
||||
'description': {'class': 'd-none d-xl-table-cell timesheet-description', 'orderBy': false},
|
||||
'unit_price': {'class': 'd-none text-nowrap text-end', 'orderBy': false},
|
||||
'duration': {'class': 'text-end text-nowrap', 'orderBy': false},
|
||||
'internalRate': {'class': 'text-end d-none text-nowrap', 'orderBy': false},
|
||||
'total_rate': {'class': 'text-end text-nowrap', 'orderBy': false},
|
||||
'actions': {'class': 'actions alwaysVisible', 'orderBy': false},
|
||||
} %}
|
||||
|
||||
{% set tableName = 'export' %}
|
||||
{% set editExported = is_granted('edit_exported_timesheet') %}
|
||||
{% set showMarkAsExportedButton = false %}
|
||||
{% set showToggleButton = false %}
|
||||
{% if entries is not empty and form.markAsExported is defined %}
|
||||
{% set showMarkAsExportedButton = form.exported.vars.value != constant('App\\Repository\\Query\\TimesheetQuery::STATE_EXPORTED') %}
|
||||
{% set showToggleButton = preview_show and form.exported.vars.value != constant('App\\Repository\\Query\\TimesheetQuery::STATE_ALL') %}
|
||||
{% endif %}
|
||||
|
||||
{% block page_title %}{{ 'export.title'|trans }}{% endblock %}
|
||||
{% block page_actions %}
|
||||
{% set event = actions(app.user, 'export', (preview_show ? 'preview' : 'index')) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% block status %}
|
||||
{% from "macros/status.html.twig" import status_duration, status_money %}
|
||||
{{ status_duration(totalDuration|duration) }}
|
||||
{% for totalCurrency, totalRate in totalRates %}
|
||||
{{ status_money(totalRate|money(totalCurrency)) }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
@@ -31,9 +53,9 @@
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% import "macros/search.html.twig" as search %}
|
||||
{% form_theme form '@AdminLTE/layout/form-theme-horizontal.html.twig' %}
|
||||
{% form_theme form 'form/horizontal.html.twig' %}
|
||||
{% block box_title %}{{ 'export.filter'|trans }}{% endblock %}
|
||||
{% block box_before %}{{ form_start(form) }}{% endblock %}
|
||||
{% block box_body %}
|
||||
@@ -69,28 +91,13 @@
|
||||
{% if entries is empty %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{% set totalDuration = 0 %}
|
||||
{% set totalRates = {} %}
|
||||
|
||||
{% for row in by_customer %}
|
||||
{% set totalDuration = totalDuration + row.duration %}
|
||||
{% set customerCurrency = row.customer.currency %}
|
||||
{% if totalRates[customerCurrency] is not defined %}
|
||||
{% set totalRates = totalRates|merge({(customerCurrency): 0.00}) %}
|
||||
{% endif %}
|
||||
{% set totalRates = totalRates|merge({(customerCurrency): totalRates[customerCurrency] + row.rate}) %}
|
||||
{% endfor %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}
|
||||
{{ 'button.preview'|trans }}: {{ 'export.title'|trans }}
|
||||
{{ widgets.label(totalDuration|duration, 'gray') }}
|
||||
{% for totalCurrency, totalRate in totalRates %}
|
||||
{{ widgets.label(totalRate|money(totalCurrency), 'gray') }}
|
||||
{% endfor %}
|
||||
{{ 'preview'|trans }}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_attributes %}id="preview_export"{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_footer %}
|
||||
{% set buttons = {} %}
|
||||
{% for button in renderer %}
|
||||
@@ -102,44 +109,64 @@
|
||||
{% set group = group|merge([button]) %}
|
||||
{% set buttons = buttons|merge({(title): group}) %}
|
||||
{% endfor %}
|
||||
<div class="btn-group" id="export-buttons" role="group">
|
||||
{% for group in buttons %}
|
||||
{% set button = group.0 %}
|
||||
{% set btnTitle = ('button.' ~ button.title)|trans %}
|
||||
{% if btnTitle == ('button.' ~ button.title) %}
|
||||
{% set btnTitle = button.title %}
|
||||
{% endif %}
|
||||
{% if group|length == 1 %}
|
||||
<button type="button" id="export-{{ button.id }}-button" class="btn btn-success startExportBtn" data-type="{{ button.id }}">
|
||||
{% if showMarkAsExportedButton %}
|
||||
<div class="d-flex">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="markAsExportedCheck" name="markAsExportedCheck" class="form-check-input" value="1" {% if form.markAsExported.vars.value == '1' %} checked="checked"{% endif %}>
|
||||
<label class="form-check-label" for="markAsExportedCheck">{{ 'mark_as_exported'|trans }}</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="d-flex">
|
||||
<div class="btn-group me-auto" id="export-buttons" role="group">
|
||||
{% for group in buttons %}
|
||||
{% set button = group.0 %}
|
||||
{% set btnTitle = ('button.' ~ button.title)|trans %}
|
||||
{% if btnTitle == ('button.' ~ button.title) %}
|
||||
{% set btnTitle = button.title %}
|
||||
{% endif %}
|
||||
{% if group|length == 1 %}
|
||||
<button type="button" id="export-{{ button.id }}-button" class="btn btn-success startExportBtn" data-type="{{ button.id }}">
|
||||
{{ btnTitle }}
|
||||
</button>
|
||||
{% elseif group|length > 1 %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ btnTitle }} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% for button in group %}
|
||||
{% elseif group|length > 1 %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-success dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ btnTitle }}
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
{% for button in group %}
|
||||
{% set btnTitle = (button.id)|trans({}, 'export') %}
|
||||
{% if btnTitle == button.id %}
|
||||
{% set btnTitle = button.id|split('.')|first|replace({'-': ' ', '_': ' '})|split(' ')|map(t => t|capitalize)|join(' ') %}
|
||||
{% endif %}
|
||||
<li><a href="#" class="startExportBtn" data-type="{{ button.id }}">{{ btnTitle }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<a href="#" class="dropdown-item startExportBtn" data-type="{{ button.id }}">{{ btnTitle }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if showToggleButton %}
|
||||
<button id="toggle-button" class="btn ms-auto d-none d-sm-inline-flex">
|
||||
{% if form.exported.vars.value == '5' %}
|
||||
{{ icon('off', true) }}
|
||||
{{ 'mark_as_exported'|trans }}
|
||||
{% elseif form.exported.vars.value == '4' %}
|
||||
{{ icon('on', true) }}
|
||||
{{ 'mark_as_open'|trans }}
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block box_body %}
|
||||
<table class="table table-hover dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th class="w-min text-right hidden-xs">{{ 'label.duration'|trans }}</th>
|
||||
{# <th class="w-min text-right hidden-xs">{{ 'label.internalRate'|trans }}</th> #}
|
||||
<th class="w-min text-right">{{ 'label.total_rate'|trans }}</th>
|
||||
<th>{{ 'customer'|trans }}</th>
|
||||
<th class="w-min text-end d-none d-sm-table-cell">{{ 'duration'|trans }}</th>
|
||||
<th class="w-min text-end">{{ 'total_rate'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -149,15 +176,10 @@
|
||||
<td>
|
||||
{{ widgets.label_customer(row.customer) }}
|
||||
</td>
|
||||
<td class="w-min text-right hidden-xs">
|
||||
<td class="w-min text-end d-none d-sm-table-cell">
|
||||
{{ row.duration|duration(decimal) }}
|
||||
</td>
|
||||
{#
|
||||
<td class="w-min text-right hidden-xs">
|
||||
{{ row.internalRate|money(currency) }}
|
||||
</td>
|
||||
#}
|
||||
<td class="w-min text-right">
|
||||
<td class="w-min text-end">
|
||||
{{ row.rate|money(currency) }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -172,7 +194,7 @@
|
||||
{% set entries = entries|slice(0, preview_limit) %}
|
||||
{% endif %}
|
||||
|
||||
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
||||
{{ tables.datatable_header(tableName, columns, query) }}
|
||||
{% for entry in entries %}
|
||||
{% set currency = entry.project.customer.currency %}
|
||||
{% if entry.fixedRate is not null %}
|
||||
@@ -181,6 +203,9 @@
|
||||
{% set rate = entry.hourlyRate %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'avatar') }}">
|
||||
{{ widgets.user_avatar(entry.user) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">
|
||||
{{ entry.begin|date_short }}
|
||||
</td>
|
||||
@@ -206,27 +231,27 @@
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}" data-duration="{{ entry.duration }}">
|
||||
{{ entry.duration|duration(decimal) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'rate_internal') }}">
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'internalRate') }}">
|
||||
{{ entry.internalRate|money(currency) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'total_rate') }}">
|
||||
{{ entry.rate|money(currency) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'exported') }}">
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
|
||||
{% if is_granted('edit_export', entry) %}
|
||||
{% if entry.exported %}
|
||||
{% if editExported %}
|
||||
<button type="button" class="btn btn-default exportBtn active" data-toggle="button" aria-pressed="true" autocomplete="off"
|
||||
data-exported-text="{{ 'entryState.exported'|trans }}" data-clean-text="{{ 'entryState.not_exported'|trans }}" data-timesheet="{{ entry.id }}">
|
||||
{{ 'entryState.exported'|trans }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary exportBtn active" data-toggle="button" aria-pressed="true"
|
||||
data-exported-text="{{ 'entryState.exported'|trans }}" data-clean-text="{{ 'entryState.not_exported'|trans }}" data-timesheet="{{ entry.id }}">
|
||||
{{ 'entryState.exported'|trans }}
|
||||
</button>
|
||||
{% else %}
|
||||
{{ 'entryState.exported'|trans }}
|
||||
{{ 'entryState.exported'|trans }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-default exportBtn" data-toggle="button" aria-pressed="false" autocomplete="off"
|
||||
<button type="button" class="btn btn-light exportBtn" data-toggle="button" aria-pressed="false"
|
||||
data-exported-text="{{ 'entryState.exported'|trans }}" data-clean-text="{{ 'entryState.not_exported'|trans }}" data-timesheet="{{ entry.id }}">
|
||||
{{ 'entryState.not_exported'|trans }}
|
||||
{{ 'entryState.not_exported'|trans }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
@@ -253,125 +278,92 @@
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
|
||||
function confirmToggleState(button)
|
||||
function updateTimesheetExportState(node, id)
|
||||
{
|
||||
var ALERT = kimai.getPlugin('alert');
|
||||
var message = '{{ 'export.clear_all'|trans }}';
|
||||
var hasActive = true;
|
||||
/** @type {KimaiAlert} */
|
||||
const ALERT = kimai.getPlugin('alert');
|
||||
/** @type {KimaiAPI} */
|
||||
const API = kimai.getPlugin('api');
|
||||
|
||||
if (!jQuery('#export-toggle-button').hasClass('export-off')) {
|
||||
message = '{{ 'export.mark_all'|trans }}';
|
||||
hasActive = false;
|
||||
}
|
||||
|
||||
ALERT.question(message, function(value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
var btn = jQuery(button);
|
||||
var exportButtons = jQuery('.exportBtn');
|
||||
// disabling does not yet work...
|
||||
if (exportButtons.length > 0) {
|
||||
btn.addClass('disabled');
|
||||
}
|
||||
|
||||
// ... as the clicks are asynchronous and the each comes back too early ...
|
||||
exportButtons.each(function () {
|
||||
if (hasActive === jQuery(this).hasClass('active')) {
|
||||
jQuery(this).click();
|
||||
}
|
||||
});
|
||||
|
||||
// ... so the button is re-enabled immediately - that should be fixed in a future update
|
||||
btn.removeClass('disabled');
|
||||
if (btn.hasClass('export-off')) {
|
||||
btn.removeClass('export-off');
|
||||
btn.html('<i class="fas fa-toggle-off"></i>');
|
||||
} else {
|
||||
btn.addClass('export-off');
|
||||
btn.html('<i class="fas fa-toggle-on"></i>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateTimesheetExportState(button, id, exported)
|
||||
{
|
||||
var ALERT = kimai.getPlugin('alert');
|
||||
|
||||
// FIXME use Kimai API plugin
|
||||
$.ajax({
|
||||
url: '{{ path('export_timesheet', {id: '-s-'}) }}'.replace('-s-', id),
|
||||
headers: {
|
||||
'X-AUTH-SESSION': true,
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
method: 'PATCH',
|
||||
success: function(data) {
|
||||
let isShowAll = jQuery('select#exported').val() === '{{ constant('App\\Repository\\Query\\TimesheetQuery::STATE_ALL') }}';
|
||||
if (exported) {
|
||||
{% if editExported %}
|
||||
button.button('exported');
|
||||
{% else %}
|
||||
if (isShowAll) {
|
||||
button.replaceWith('{{ 'entryState.exported'|trans }}');
|
||||
}
|
||||
{% endif %}
|
||||
} else {
|
||||
button.button('clean');
|
||||
}
|
||||
if (!isShowAll) {
|
||||
button.closest('tr').hide('ease', function() {
|
||||
jQuery(this).remove();
|
||||
if(jQuery(this).closest('table').find('tbody tr:visible').length === 0) {
|
||||
jQuery('#export-buttons button').prop('disabled', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, error: function(jqXHR, textStatus, errorThrown) {
|
||||
var message = 'An error occured';
|
||||
if (jqXHR.responseJSON !== undefined) {
|
||||
message = jqXHR.responseJSON.message;
|
||||
if (jqXHR.responseJSON.errors !== undefined)
|
||||
{
|
||||
var errors = jqXHR.responseJSON.errors.errors;
|
||||
for (var i = 0; i < errors.length; i++) {
|
||||
message += ' / ' + errors[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
ALERT.error('{{ 'action.update.error'|trans({}, 'flashmessages') }}', message);
|
||||
button.button('reset');
|
||||
if (exported) {
|
||||
button.removeClass('active');
|
||||
} else {
|
||||
button.addClass('active');
|
||||
API.patch(
|
||||
'{{ path('export_timesheet', {id: 1}) }}'.replace('1', id),
|
||||
{},
|
||||
function(data) {
|
||||
const table = node.closest('table')
|
||||
const row = node.closest('tr');
|
||||
row.remove();
|
||||
const list = table.querySelectorAll('tbody tr');
|
||||
if (list.length === 0) {
|
||||
document.querySelector('form#export-form button.performSearch').click();
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
jQuery('body').on('click', '.exportBtn', function() {
|
||||
var button = jQuery(this);
|
||||
var id = button.attr('data-timesheet');
|
||||
updateTimesheetExportState(button, id, !button.hasClass('active'));
|
||||
});
|
||||
document.addEventListener('click', function (event) {
|
||||
const node = event.target;
|
||||
if (node.matches('.exportBtn')) {
|
||||
if (node.dataset['timesheet'] !== undefined) {
|
||||
const id = node.dataset['timesheet'];
|
||||
updateTimesheetExportState(node, id);
|
||||
}
|
||||
}
|
||||
|
||||
jQuery('#export-toggle-button').on('click', function () {
|
||||
confirmToggleState(this);
|
||||
});
|
||||
{#
|
||||
THE GROUP OF EXPORT BUTTONS FOR ALL FORMATS
|
||||
Toggle the form, because by default it triggers the search
|
||||
#}
|
||||
if (node.matches('#export-buttons .startExportBtn')) {
|
||||
document.getElementById('renderer').value = node.dataset['type'];
|
||||
const form = document.getElementById("export-form");
|
||||
const previousAction = form.action;
|
||||
const previousMethod = form.method;
|
||||
const previousTarget = form.target;
|
||||
form.target = '_blank';
|
||||
form.method = 'POST';
|
||||
form.action = '{{ path('export_data') }}';
|
||||
form.submit();
|
||||
document.getElementById('renderer').value = '';
|
||||
form.target = previousTarget;
|
||||
form.method = previousMethod;
|
||||
form.action = previousAction;
|
||||
}
|
||||
}, false);
|
||||
|
||||
jQuery('body').on('click', '#export-buttons .startExportBtn', function() {
|
||||
jQuery('#renderer').val(jQuery(this).attr('data-type'));
|
||||
var $form = jQuery("#export-form");
|
||||
var prevAction = $form.attr('action');
|
||||
var prevMethod = $form.attr('method');
|
||||
$form.attr('target', '_blank').attr('method', 'POST').attr('action', '{{ path('export_data') }}');
|
||||
$form.submit();
|
||||
jQuery('#renderer').val('');
|
||||
$form.removeAttr('target').attr('action', prevAction).attr('method', prevMethod);
|
||||
});
|
||||
{% if showMarkAsExportedButton %}
|
||||
document.getElementById('markAsExportedCheck').addEventListener('change', function(event) {
|
||||
document.getElementById('markAsExported').value = event.target.checked ? 1 : 0;
|
||||
});
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
{% if showToggleButton %}
|
||||
function confirmToggleState(doExport)
|
||||
{
|
||||
let ALERT = kimai.getPlugin('alert');
|
||||
let message = '{{ 'export.clear_all'|trans }}';
|
||||
|
||||
if (doExport) {
|
||||
message = '{{ 'export.mark_all'|trans }}';
|
||||
}
|
||||
|
||||
ALERT.question(message, function(value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
[].slice.call(document.querySelectorAll('.exportBtn')).map(function (element) {
|
||||
element.click();
|
||||
});
|
||||
|
||||
document.getElementById('toggle-button').classList.add('d-none');
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('toggle-button').addEventListener('click', function (event) {
|
||||
confirmToggleState({% if form.exported.vars.value == '5' %}true{% else %}false{% endif %});
|
||||
}, false);
|
||||
{% endif %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||
<link rel="stylesheet" href="{{ asset('bundles/tabler/tabler.css') }}">
|
||||
{{ encore_entry_link_tags('app') }}
|
||||
{{ encore_entry_script_tags('app') }}
|
||||
{% block styles %}{% endblock %}
|
||||
@@ -17,6 +18,8 @@
|
||||
{% block export %}{% endblock %}
|
||||
</section>
|
||||
</div>
|
||||
{% block javascripts %}{% endblock %}
|
||||
{% block javascripts %}
|
||||
<script src="{{ asset('bundles/tabler/tabler.js') }}"></script>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -18,61 +18,7 @@
|
||||
<head>
|
||||
{% block styles %}
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
font-size: 10pt;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
table.items {
|
||||
border: 0.1mm solid #000000;
|
||||
width: 100%;
|
||||
font-size: 9pt;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
td, th {
|
||||
padding: 7px;
|
||||
}
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
.items td {
|
||||
border-left: 0.1mm solid #000000;
|
||||
border-right: 0.1mm solid #000000;
|
||||
}
|
||||
.items tr.even {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.items tr.summary {
|
||||
background-color: #efefef;
|
||||
}
|
||||
.items tr.summary td {
|
||||
font-weight: bold;
|
||||
border-top: 0.1mm solid #000000;
|
||||
border-bottom: 0.1mm solid #000000;
|
||||
}
|
||||
table thead th {
|
||||
background-color: #ececec;
|
||||
border: 0.1mm solid #000000;
|
||||
font-weight: bold;
|
||||
font-size: 10pt;
|
||||
text-align: left;
|
||||
}
|
||||
.items td.totals {
|
||||
font-weight: bold;
|
||||
border: 0.1mm solid #000000;
|
||||
}
|
||||
.items .center,
|
||||
.items td.duration,
|
||||
.items td.cost {
|
||||
text-align: center;
|
||||
}
|
||||
.text-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
{{ encore_entry_css_source('export-pdf')|raw }}
|
||||
</style>
|
||||
{% endblock %}
|
||||
</head>
|
||||
@@ -86,15 +32,15 @@
|
||||
{{ 'export.page_of'|trans({'%page%': '{PAGENO}', '%pages%': '{nb}'}) }}
|
||||
{% if not showUserColumn and query is defined and query.user %}
|
||||
–
|
||||
{{ 'label.user'|trans }}: {{ query.user.displayName }}
|
||||
{{ 'user'|trans }}: {{ query.user.displayName }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td align="right">
|
||||
{% set company = config('theme.branding.company') %}
|
||||
{% if company is not empty %}
|
||||
{{ company|raw }} – {{ now|date_full }}
|
||||
{{ company }} – {{ now|date_time }}
|
||||
{% else %}
|
||||
{{ 'export.date_copyright'|trans({'%date%': now|date_full, '%kimai%': '<a href="' ~ constant('App\\Constants::HOMEPAGE') ~ '">' ~ constant('App\\Constants::SOFTWARE') ~ '</a>'})|raw }}
|
||||
{{ 'export.date_copyright'|trans({'%date%': now|date_time, '%kimai%': '<a href="' ~ constant('App\\Constants::HOMEPAGE') ~ '">' ~ constant('App\\Constants::SOFTWARE') ~ '</a>'})|raw }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -118,20 +64,20 @@ mpdf-->
|
||||
<table class="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th>{{ 'label.project'|trans }}</th>
|
||||
<th>{{ 'customer'|trans }}</th>
|
||||
<th>{{ 'project'|trans }}</th>
|
||||
{% if showTimeBudget %}
|
||||
<th class="center">{{ 'label.timeBudget'|trans }}</th>
|
||||
<th class="center">{{ 'timeBudget'|trans }}</th>
|
||||
{% endif %}
|
||||
{% if showRateBudget %}
|
||||
<th class="center">{{ 'label.budget'|trans }}</th>
|
||||
<th class="center">{{ 'budget'|trans }}</th>
|
||||
{% endif %}
|
||||
<th class="center">{{ 'label.duration'|trans }}</th>
|
||||
<th class="center">{{ 'duration'|trans }}</th>
|
||||
{% if showRateColumn %}
|
||||
{% if showInternalRate %}
|
||||
<th class="center">{{ 'label.rate_internal'|trans }}</th>
|
||||
<th class="center">{{ 'internalRate'|trans }}</th>
|
||||
{% endif %}
|
||||
<th class="center">{{ 'label.rate'|trans }}</th>
|
||||
<th class="center">{{ 'rate'|trans }}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -268,17 +214,17 @@ mpdf-->
|
||||
<table class="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.date'|trans }}</th>
|
||||
<th>{{ 'date'|trans }}</th>
|
||||
{% if showUserColumn %}
|
||||
<th>{{ 'label.user'|trans }}</th>
|
||||
<th>{{ 'user'|trans }}</th>
|
||||
{% endif %}
|
||||
<th>{{ 'label.description'|trans }}</th>
|
||||
<th class="center">{{ 'label.duration'|trans }}</th>
|
||||
<th>{{ 'description'|trans }}</th>
|
||||
<th class="center">{{ 'duration'|trans }}</th>
|
||||
{% if showRateColumn %}
|
||||
{% if showInternalRate %}
|
||||
<th class="center">{{ 'label.rate_internal'|trans }}</th>
|
||||
<th class="center">{{ 'internalRate'|trans }}</th>
|
||||
{% endif %}
|
||||
<th class="center">{{ 'label.rate'|trans }}</th>
|
||||
<th class="center">{{ 'rate'|trans }}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/webloader.html.twig" as webloader %}
|
||||
|
||||
{% block document_title %}{{ 'export.title'|trans }}{% endblock %}
|
||||
{% block document_title %}{{ 'export'|trans }}{% endblock %}
|
||||
|
||||
{% set storageItemName = 'kimai_html_export' %}
|
||||
|
||||
@@ -46,9 +46,9 @@
|
||||
{% set columns = columns|merge({
|
||||
'hourlyRate': false,
|
||||
'fixedRate': false,
|
||||
'duration': 'label.duration',
|
||||
'rate_internal': 'label.rate_internal',
|
||||
'rate': 'label.rate',
|
||||
'duration': 'duration',
|
||||
'internalRate': 'internalRate',
|
||||
'rate': 'rate',
|
||||
}) %}
|
||||
|
||||
{% block javascripts %}
|
||||
@@ -57,20 +57,17 @@
|
||||
let initialized = false;
|
||||
|
||||
document.addEventListener('kimai.initialized', function(event) {
|
||||
const KIMAI = event.detail.kimai;
|
||||
const DATES = KIMAI.getPlugin('date');
|
||||
|
||||
document.getElementById('duration-decimal').addEventListener('click', function(event) {
|
||||
let spans = document.getElementsByClassName('duration-format');
|
||||
for (let span of spans) {
|
||||
let duration = span.dataset['duration'];
|
||||
if (duration === undefined) {
|
||||
continue;
|
||||
}
|
||||
var spans = document.getElementsByClassName('duration-format');
|
||||
for (var span of spans) {
|
||||
if (!event.target.checked) {
|
||||
span.innerHTML = DATES.formatSeconds(duration);
|
||||
if (span.dataset['duration'] !== undefined) {
|
||||
span.innerHTML = span.dataset['duration'];
|
||||
}
|
||||
} else {
|
||||
span.innerHTML = (new Intl.NumberFormat('{{ app.request.locale|replace({'_': '-'}) }}', {maximumFractionDigits: 2}).format(duration / 3600));
|
||||
if (span.dataset['durationDecimal'] !== undefined) {
|
||||
span.innerHTML = span.dataset['durationDecimal'];
|
||||
}
|
||||
}
|
||||
}
|
||||
saveVisibility();
|
||||
@@ -122,8 +119,8 @@
|
||||
}
|
||||
saveVisibility();
|
||||
});
|
||||
document.getElementById('summary-rate-internal').addEventListener('click', function(event) {
|
||||
let cells = document.getElementsByClassName('summary-rate-internal');
|
||||
document.getElementById('summary-internalRate').addEventListener('click', function(event) {
|
||||
let cells = document.getElementsByClassName('summary-internalRate');
|
||||
for (let columnCell of cells) {
|
||||
columnCell.style.display = event.target.checked ? 'table-cell' : 'none';
|
||||
}
|
||||
@@ -189,7 +186,7 @@
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore error in restoring
|
||||
console.log('Failed to restore settings, clearing all up: ', e);
|
||||
console.log('Failed to restore settings, removing invalid settings', e);
|
||||
localStorage.removeItem('{{ storageItemName }}');
|
||||
}
|
||||
}
|
||||
@@ -261,7 +258,6 @@
|
||||
h3 { font-size: 18px; }
|
||||
p, td { font-size: 11px; }
|
||||
th, td.totals { font-size: 12px; }
|
||||
table.table-bordered.dataTable { border-collapse: collapse!important; }
|
||||
}
|
||||
.items td.totals {
|
||||
font-weight: bold;
|
||||
@@ -272,7 +268,7 @@
|
||||
.items th.cost,
|
||||
.items td.cost,
|
||||
.items th.column-rate,
|
||||
.items th.column-rate_internal,
|
||||
.items th.column-internalRate,
|
||||
.items th.column-duration
|
||||
{
|
||||
text-align: right;
|
||||
@@ -284,115 +280,113 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block export %}
|
||||
<form class="form-inline no-print">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{% set formItemClass = "d-inline-block w-auto text-nowrap me-2" %}
|
||||
<form class="w-100 d-print-none row row-cards p-3">
|
||||
<div class="card mb-1 p-0">
|
||||
<div class="card-header">
|
||||
{{ 'settings'|trans({}, 'actions') }}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<label class="control-label" for="duration-decimal">
|
||||
<div class="card-body">
|
||||
<label for="duration-decimal">
|
||||
<input type="checkbox" id="duration-decimal" name="duration-decimal"{% if decimal%} checked="checked"{% endif %}>
|
||||
{{ 'label.timesheet.export_decimal'|trans }}
|
||||
{{ 'export_decimal'|trans }}
|
||||
</label>
|
||||
|
|
||||
<label class="control-label" for="date-format">
|
||||
{{ 'label.date'|trans }}:
|
||||
<label for="date-format">
|
||||
{{ 'date'|trans }}:
|
||||
{% set demo_date = create_date('2020-01-01 20:00:00') %}
|
||||
<select id="date-format" name="date-format">
|
||||
<option value="time">{{ demo_date|date_time }}</option>
|
||||
<option value="short">{{ demo_date|date_short }}</option>
|
||||
<option value="full">{{ demo_date|date_full }}</option>
|
||||
<option value="time">{{ demo_date|date_time }}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
|
||||
<label class="control-label" for="begin-format">
|
||||
{{ 'label.begin'|trans }}:
|
||||
<label for="begin-format">
|
||||
{{ 'begin'|trans }}:
|
||||
{% set demo_date = create_date('2020-01-01 11:00:00') %}
|
||||
<select id="begin-format" name="begin-format">
|
||||
<option value="plain">{{ demo_date|date_format('H:i') }}</option>
|
||||
<option value="time">{{ demo_date|date_time }}</option>
|
||||
<option value="short">{{ demo_date|date_short }}</option>
|
||||
<option value="full">{{ demo_date|date_full }}</option>
|
||||
<option value="time">{{ demo_date|date_time }}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
|
||||
<label class="control-label" for="end-format">
|
||||
{{ 'label.end'|trans }}:
|
||||
<label for="end-format">
|
||||
{{ 'end'|trans }}:
|
||||
{% set demo_date = create_date('2020-01-01 18:00:00') %}
|
||||
<select id="end-format" name="end-format">
|
||||
<option value="plain">{{ demo_date|date_format('H:i') }}</option>
|
||||
<option value="time">{{ demo_date|date_time }}</option>
|
||||
<option value="short">{{ demo_date|date_short }}</option>
|
||||
<option value="full">{{ demo_date|date_full }}</option>
|
||||
<option value="time">{{ demo_date|date_time }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="reset" class="btn btn-primary pull-right" onclick="resetExport()">{{ 'action.reset'|trans }}</button>
|
||||
<button type="reset" class="btn btn-primary btn-sm pull-right" onclick="resetExport()">{{ 'action.reset'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<label class="control-label" for="summary-show">
|
||||
<div class="card mb-1 p-0">
|
||||
<div class="card-header">
|
||||
<label for="summary-show">
|
||||
<input type="checkbox" id="summary-show" name="summary-show" checked="checked">
|
||||
{{ 'export.summary'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="summary-by-activities">
|
||||
<div class="card-body w-100">
|
||||
<div class="{{ formItemClass }}">
|
||||
<label for="summary-by-activities">
|
||||
<input type="checkbox" id="summary-by-activities" name="summary-by-activities">
|
||||
{{ 'label.activity'|trans }}
|
||||
{{ 'activity'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="summary-timeBudget">
|
||||
<div class="{{ formItemClass }}">
|
||||
<label for="summary-timeBudget">
|
||||
<input type="checkbox" id="summary-timeBudget" name="summary-timeBudget" checked>
|
||||
{{ 'label.timeBudget'|trans }}
|
||||
{{ 'timeBudget'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="summary-budget">
|
||||
<div class="{{ formItemClass }}">
|
||||
<label for="summary-budget">
|
||||
<input type="checkbox" id="summary-budget" name="summary-budget" checked>
|
||||
{{ 'label.budget'|trans }}
|
||||
{{ 'budget'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="summary-duration">
|
||||
<div class="{{ formItemClass }}">
|
||||
<label for="summary-duration">
|
||||
<input type="checkbox" id="summary-duration" name="summary-duration" checked>
|
||||
{{ 'label.duration'|trans }}
|
||||
{{ 'duration'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="summary-rate-internal">
|
||||
<input type="checkbox" id="summary-rate-internal" name="summary-rate-internal" checked>
|
||||
{{ 'label.rate_internal'|trans }}
|
||||
<div class="{{ formItemClass }}">
|
||||
<label for="summary-internalRate">
|
||||
<input type="checkbox" id="summary-internalRate" name="summary-internalRate" checked>
|
||||
{{ 'internalRate'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="summary-rate">
|
||||
<div class="{{ formItemClass }}">
|
||||
<label for="summary-rate">
|
||||
<input type="checkbox" id="summary-rate" name="summary-rate" checked>
|
||||
{{ 'label.rate'|trans }}
|
||||
{{ 'rate'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="records-show">
|
||||
<div class="card mb-1 p-0">
|
||||
<div class="card-header">
|
||||
<div class="{{ formItemClass }}">
|
||||
<label for="records-show">
|
||||
<input type="checkbox" id="records-show" name="records-show" checked="checked">
|
||||
{{ 'export.full_list'|trans }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="card-body">
|
||||
{% for columnId, visibility in columns %}
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="records-column-{{ columnId }}">
|
||||
<div class="{{ formItemClass }}">
|
||||
<label for="records-column-{{ columnId }}">
|
||||
<input type="checkbox" class="column-visibility-changer" id="records-column-{{ columnId }}" name="{{ columnId }}" {% if visibility %}checked="checked"{% endif %}>
|
||||
{{ columnTitles[columnId]|default('label.'~columnId)|trans }}
|
||||
{{ columnTitles[columnId]|default(columnId)|trans }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -400,7 +394,7 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row">
|
||||
<div class="row m-3">
|
||||
<div class="col-xs-12">
|
||||
<h2 id="doc-title" contenteditable="true" data-original="{{ 'export.document_title'|trans }}">{{ 'export.document_title'|trans }}</h2>
|
||||
<p>
|
||||
@@ -410,19 +404,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="export-summary">
|
||||
<div class="col-xs-12">
|
||||
<h3 id="doc-summary" contenteditable="true" data-original="{{ 'export.summary'|trans }}">{{ 'export.summary'|trans }}</h3>
|
||||
<table class="items table table-condensed table-bordered dataTable" id="summary-project">
|
||||
<div class="row m-3" id="export-summary">
|
||||
<div class="card p-0">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title" id="doc-summary" contenteditable="true" data-original="{{ 'export.summary'|trans }}">{{ 'export.summary'|trans }}</h3>
|
||||
</div>
|
||||
<table class="items table table-sm table-bordered dataTable" id="summary-project">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th>{{ 'label.project'|trans }}</th>
|
||||
<th class="center export-timeBudget">{{ 'label.timeBudget'|trans }}</th>
|
||||
<th class="center export-budget">{{ 'label.budget'|trans }}</th>
|
||||
<th class="duration summary-duration">{{ 'label.duration'|trans }}</th>
|
||||
<th class="cost summary-rate-internal">{{ 'label.rate_internal'|trans }}</th>
|
||||
<th class="cost summary-rate">{{ 'label.rate'|trans }}</th>
|
||||
<th>{{ 'customer'|trans }}</th>
|
||||
<th>{{ 'project'|trans }}</th>
|
||||
<th class="center export-timeBudget">{{ 'timeBudget'|trans }}</th>
|
||||
<th class="center export-budget">{{ 'budget'|trans }}</th>
|
||||
<th class="duration summary-duration">{{ 'duration'|trans }}</th>
|
||||
<th class="cost summary-internalRate">{{ 'internalRate'|trans }}</th>
|
||||
<th class="cost summary-rate">{{ 'rate'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -437,14 +433,16 @@
|
||||
{% set customerCurrency = summary.currency %}
|
||||
{% endif %}
|
||||
{% if customer is not same as(summary.customer) %}
|
||||
{% set durationNormal = customerDuration|duration(false) %}
|
||||
{% set durationDecimal = customerDuration|duration(true) %}
|
||||
<tr class="summary">
|
||||
<td colspan="2"> </td>
|
||||
<td class="export-timeBudget"></td>
|
||||
<td class="export-budget"></td>
|
||||
<td class="totals duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ customerDuration }}">{{ customerDuration|duration(decimal) }}</span>
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
</td>
|
||||
<td class="totals cost summary-rate-internal">{{ customerInternalRate|money(customerCurrency) }}</td>
|
||||
<td class="totals cost summary-internalRate">{{ customerInternalRate|money(customerCurrency) }}</td>
|
||||
<td class="totals cost summary-rate">{{ customerRate|money(customerCurrency) }}</td>
|
||||
</tr>
|
||||
{% set customerCurrency = summary.currency %}
|
||||
@@ -458,7 +456,9 @@
|
||||
<td>{{ summary.project }}</td>
|
||||
<td class="center export-timeBudget">
|
||||
{% if budgets[id] is defined and budgets[id].time_left > 0 %}
|
||||
<span class="duration-format" data-duration="{{ budgets[id].time_left }}">{{ budgets[id].time_left|duration(decimal) }}</span>
|
||||
{% set durationNormal = budgets[id].time_left|duration(false) %}
|
||||
{% set durationDecimal = budgets[id].time_left|duration(true) %}
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="center export-budget">
|
||||
@@ -467,9 +467,11 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ summary.duration }}">{{ summary.duration|duration(decimal) }}</span>
|
||||
{% set durationNormal = summary.duration|duration(false) %}
|
||||
{% set durationDecimal = summary.duration|duration(true) %}
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
</td>
|
||||
<td class="cost summary-rate-internal">{{ summary.rate_internal|money(summary.currency) }}</td>
|
||||
<td class="cost summary-internalRate">{{ summary.rate_internal|money(summary.currency) }}</td>
|
||||
<td class="cost summary-rate">{{ summary.rate|money(summary.currency) }}</td>
|
||||
</tr>
|
||||
{% set customerDuration = customerDuration + summary.duration %}
|
||||
@@ -477,14 +479,16 @@
|
||||
{% set customerInternalRate = customerInternalRate + summary.rate_internal %}
|
||||
{% endfor %}
|
||||
{% if customer is not same as(null) %}
|
||||
{% set durationNormal = customerDuration|duration(false) %}
|
||||
{% set durationDecimal = customerDuration|duration(true) %}
|
||||
<tr class="summary">
|
||||
<td colspan="2"> </td>
|
||||
<td class="export-timeBudget"></td>
|
||||
<td class="export-budget"></td>
|
||||
<td class="totals duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ customerDuration }}">{{ customerDuration|duration(decimal) }}</span>
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
</td>
|
||||
<td class="totals cost summary-rate-internal">{{ customerInternalRate|money(customerCurrency) }}</td>
|
||||
<td class="totals cost summary-internalRate">{{ customerInternalRate|money(customerCurrency) }}</td>
|
||||
<td class="totals cost summary-rate">{{ customerRate|money(customerCurrency) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -497,17 +501,17 @@
|
||||
============================================
|
||||
#}
|
||||
|
||||
<table class="items table table-condensed table-bordered dataTable" id="summary-activity" style="display:none">
|
||||
<table class="items table table-sm table-bordered dataTable" id="summary-activity" style="display:none">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th>{{ 'label.project'|trans }}</th>
|
||||
<th>{{ 'label.activity'|trans }}</th>
|
||||
<th class="center export-timeBudget">{{ 'label.timeBudget'|trans }}</th>
|
||||
<th class="center export-budget">{{ 'label.budget'|trans }}</th>
|
||||
<th class="duration summary-duration">{{ 'label.duration'|trans }}</th>
|
||||
<th class="cost summary-rate-internal">{{ 'label.rate_internal'|trans }}</th>
|
||||
<th class="cost summary-rate">{{ 'label.rate'|trans }}</th>
|
||||
<th>{{ 'customer'|trans }}</th>
|
||||
<th>{{ 'project'|trans }}</th>
|
||||
<th>{{ 'activity'|trans }}</th>
|
||||
<th class="center export-timeBudget">{{ 'timeBudget'|trans }}</th>
|
||||
<th class="center export-budget">{{ 'budget'|trans }}</th>
|
||||
<th class="duration summary-duration">{{ 'duration'|trans }}</th>
|
||||
<th class="cost summary-internalRate">{{ 'internalRate'|trans }}</th>
|
||||
<th class="cost summary-rate">{{ 'rate'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -527,9 +531,11 @@
|
||||
<td class="export-timeBudget"></td>
|
||||
<td class="export-budget"></td>
|
||||
<td class="totals duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ customerDuration }}">{{ customerDuration|duration(decimal) }}</span>
|
||||
{% set durationNormal = customerDuration|duration(false) %}
|
||||
{% set durationDecimal = customerDuration|duration(true) %}
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
</td>
|
||||
<td class="totals cost summary-rate-internal">{{ customerInternalRate|money(customerCurrency) }}</td>
|
||||
<td class="totals cost summary-internalRate">{{ customerInternalRate|money(customerCurrency) }}</td>
|
||||
<td class="totals cost summary-rate">{{ customerRate|money(customerCurrency) }}</td>
|
||||
</tr>
|
||||
{% set customerCurrency = summary.currency %}
|
||||
@@ -546,7 +552,9 @@
|
||||
{% if activity_budgets[id] is defined and activity_budgets[id][activityId] is defined %}
|
||||
<td class="center export-timeBudget">
|
||||
{% if activity_budgets[id][activityId].time_left > 0 %}
|
||||
<span class="duration-format" data-duration="{{ activity_budgets[id][activityId].time_left }}">{{ activity_budgets[id][activityId].time_left|duration(decimal) }}</span>
|
||||
{% set durationNormal = activity_budgets[id][activityId].time_left|duration(false) %}
|
||||
{% set durationDecimal = activity_budgets[id][activityId].time_left|duration(true) %}
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="center export-budget">
|
||||
@@ -559,9 +567,11 @@
|
||||
<td class="center export-budget"></td>
|
||||
{% endif %}
|
||||
<td class="duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ activitySummary.duration }}">{{ activitySummary.duration|duration(decimal) }}</span>
|
||||
{% set durationNormal = activitySummary.duration|duration(false) %}
|
||||
{% set durationDecimal = activitySummary.duration|duration(true) %}
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
</td>
|
||||
<td class="cost summary-rate-internal">{{ activitySummary.rate_internal|money(activitySummary.currency) }}</td>
|
||||
<td class="cost summary-internalRate">{{ activitySummary.rate_internal|money(activitySummary.currency) }}</td>
|
||||
<td class="cost summary-rate">{{ activitySummary.rate|money(activitySummary.currency) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -575,9 +585,11 @@
|
||||
<td class="export-timeBudget"></td>
|
||||
<td class="export-budget"></td>
|
||||
<td class="totals duration summary-duration">
|
||||
<span class="duration-format" data-duration="{{ customerDuration }}">{{ customerDuration|duration(decimal) }}</span>
|
||||
{% set durationNormal = customerDuration|duration(false) %}
|
||||
{% set durationDecimal = customerDuration|duration(true) %}
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
</td>
|
||||
<td class="totals cost summary-rate-internal">{{ customerInternalRate|money(customerCurrency) }}</td>
|
||||
<td class="totals cost summary-internalRate">{{ customerInternalRate|money(customerCurrency) }}</td>
|
||||
<td class="totals cost summary-rate">{{ customerRate|money(customerCurrency) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -586,18 +598,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="export-records">
|
||||
<div class="col-xs-12">
|
||||
<h3 id="doc-records" contenteditable="true" data-original="{{ 'export.full_list'|trans }}">{{ 'export.full_list'|trans }}</h3>
|
||||
<table class="items table table-condensed table-bordered dataTable">
|
||||
<div class="row m-3" id="export-records">
|
||||
<div class="card p-0">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title" id="doc-records" contenteditable="true" data-original="{{ 'export.full_list'|trans }}">{{ 'export.full_list'|trans }}</h3>
|
||||
</div>
|
||||
<table class="items table table-sm table-bordered dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for columnId, visibility in columns %}
|
||||
<th class="column column-{{ columnId }}" {% if not visibility %}style="display: none"{% endif %}>
|
||||
{{ columnTitles[columnId]|default('label.'~columnId)|trans }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
<tr>
|
||||
{% for columnId, visibility in columns %}
|
||||
<th class="column column-{{ columnId }}" {% if not visibility %}style="display: none"{% endif %}>
|
||||
{{ columnTitles[columnId]|default(columnId)|trans }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set timeWorked = 0 %}
|
||||
@@ -620,17 +634,17 @@
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="column-date text-nowrap" {% if not columns.date %}style="display: none"{% endif %}>
|
||||
<span class="dateformat" data-short="{{ entry.begin|date_short }}" data-full="{{ entry.begin|date_full }}" data-time="{{ entry.begin|date_time }}">
|
||||
<span class="dateformat" data-short="{{ entry.begin|date_short }}" data-full="{{ entry.begin|date_time }}" data-time="{{ entry.begin|date_time }}">
|
||||
{{ entry.begin|date_time }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="column-begin text-nowrap" {% if not columns.begin %}style="display: none"{% endif %}>
|
||||
<span class="beginformat" data-short="{{ entry.begin|date_short }}" data-full="{{ entry.begin|date_full }}" data-time="{{ entry.begin|date_time }}" data-plain="{{ entry.begin|date_format('H:i') }}">
|
||||
<span class="beginformat" data-short="{{ entry.begin|date_short }}" data-full="{{ entry.begin|date_time }}" data-time="{{ entry.begin|date_time }}" data-plain="{{ entry.begin|date_format('H:i') }}">
|
||||
{{ entry.begin|date_format('H:i') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="column-end text-nowrap" {% if not columns.end %}style="display: none"{% endif %}>
|
||||
<span class="endformat" data-short="{{ entry.end|date_short }}" data-full="{{ entry.end|date_full }}" data-time="{{ entry.end|date_time }}" data-plain="{{ entry.end|date_format('H:i') }}">
|
||||
<span class="endformat" data-short="{{ entry.end|date_short }}" data-full="{{ entry.end|date_time }}" data-time="{{ entry.end|date_time }}" data-plain="{{ entry.end|date_format('H:i') }}">
|
||||
{{ entry.end|date_format('H:i') }}
|
||||
</span>
|
||||
</td>
|
||||
@@ -701,9 +715,11 @@
|
||||
{{ entry.fixedRate|money(entry.project.customer.currency) }}
|
||||
</td>
|
||||
<td class="duration column-duration text-nowrap" {% if not columns.duration %}style="display: none"{% endif %}>
|
||||
<span class="duration-format" data-duration="{{ entry.duration }}">{{ entry.duration|duration(decimal) }}</span>
|
||||
{% set durationNormal = entry.duration|duration(false) %}
|
||||
{% set durationDecimal = entry.duration|duration(true) %}
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
</td>
|
||||
<td class="cost column-rate_internal text-nowrap" {% if not columns.rate_internal %}style="display: none"{% endif %}>
|
||||
<td class="cost column-internalRate text-nowrap" {% if not columns.internalRate %}style="display: none"{% endif %}>
|
||||
{% if entry.internalRate is defined %}
|
||||
{{ entry.internalRate|money(entry.project.customer.currency) }}
|
||||
{% else %}
|
||||
@@ -718,14 +734,16 @@
|
||||
{# leave in tbody instead of adding it to tfoot, as tfoot will be repeated on each page when printing #}
|
||||
<tr>
|
||||
{% for id, visibility in columns %}
|
||||
{% if id not in ['duration', 'rate', 'rate_internal'] %}
|
||||
{% if id not in ['duration', 'rate', 'internalRate'] %}
|
||||
<th class="column-{{ id }}" {% if not visibility %}style="display: none"{% endif %}></th>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<th class="text-nowrap column-duration">
|
||||
<span class="duration-format" data-duration="{{ timeWorked }}">{{- timeWorked|duration(decimal) -}}</span>
|
||||
{% set durationNormal = timeWorked|duration(false) %}
|
||||
{% set durationDecimal = timeWorked|duration(true) %}
|
||||
<span class="duration-format" data-duration-decimal="{{ durationDecimal }}" data-duration="{{ durationNormal }}">{{ decimal ? durationDecimal : durationNormal }}</span>
|
||||
</th>
|
||||
<th class="text-nowrap column-rate_internal">
|
||||
<th class="text-nowrap column-internalRate">
|
||||
{%- if currency is not null and currency is not same as(false) %}
|
||||
{{ rateInternalTotal|money(currency) }}
|
||||
{% else %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</head>
|
||||
<body class="ajax-forms">
|
||||
<div id="ajax-messages">
|
||||
{{ include('@AdminLTE/Partials/_flash_messages.html.twig') }}
|
||||
{{ include('@theme/includes/flash_messages.html.twig') }}
|
||||
</div>
|
||||
<div id="ajax-form">
|
||||
{{ block('main') }}
|
||||
|
||||
352
templates/form/blocks.html.twig
Normal file
352
templates/form/blocks.html.twig
Normal file
@@ -0,0 +1,352 @@
|
||||
|
||||
{% block quick_entry_week_row %}
|
||||
<tr{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' form-group qe-entry-week-row' ~ (not valid ? ' is-invalid'))|trim})} %}{{ block('attributes') }}{% endwith %}>
|
||||
<td>
|
||||
{{ form_row(form.project) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ form_row(form.activity) }}
|
||||
</td>
|
||||
{% for timesheet in form.timesheets %}
|
||||
<td class="text-center{% if timesheet.vars.data.begin is weekend %} weekend{% endif %}{% if timesheet.vars.data.begin is today %} today{% endif %}">
|
||||
{{ form_widget(timesheet) }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td class="text-nowrap text-center total qe-totals-row"></td>
|
||||
</tr>
|
||||
{% endblock %}
|
||||
|
||||
{% block _team_edit_form_members_entry_user_widget %}
|
||||
{# this will convert the select box into a hidden field, which are exchangeable from an HTML perspective #}
|
||||
{%- set type = 'hidden' -%}
|
||||
{{ block('form_widget_simple') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block _team_edit_form_members_entry_teamlead_widget %}
|
||||
{% set attr = attr|merge({'class' : 'form-selectgroup-input '}) %}
|
||||
<input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
|
||||
{% endblock %}
|
||||
|
||||
{% block _team_edit_form_members_widget %}
|
||||
<p>{{ 'team.configure_teamlead.help'|trans }}</p>
|
||||
{{ form_widget(form, {row_attr: {class: 'mb-1'}}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block team_member_row %}
|
||||
{{ form_widget(form) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block team_member_widget %}
|
||||
{% import 'macros/widgets.html.twig' as widgets%}
|
||||
{% set data = {} %}
|
||||
{% set user = null %}
|
||||
{% if form.vars.data is not null and form.vars.data.user is not null %}
|
||||
{% set user = form.vars.data.user %}
|
||||
{% set data = {id: user.id, color: user.color|colorize(user.displayName), title: user.title, username: user.userIdentifier, alias: user.alias, initials: user.initials, accountnumber: user.accountNumber, display: user.displayName} %}
|
||||
{% endif %}
|
||||
<div class="row mb-1"{% for key, value in data %} data-{{ key }}="{{ value }}"{% endfor %}>
|
||||
<div class="col-xs-12">
|
||||
<label class="form-selectgroup-item flex-fill">
|
||||
{{ form_widget(form.teamlead) }}
|
||||
<div class="form-selectgroup-label d-flex align-items-center p-3">
|
||||
<div class="me-3">
|
||||
<span class="form-selectgroup-check"></span>
|
||||
</div>
|
||||
<div class="form-selectgroup-label-content d-flex align-items-center me-2">
|
||||
{% if user is not null %}
|
||||
{{ widgets.user_avatar(form.vars.data.user) }}
|
||||
{% else %}
|
||||
{{ widgets.avatar('__INITIALS__', '__COLOR__', '__DISPLAY__') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-selectgroup-label-content d-flex align-items-center">
|
||||
<div class="text-start">
|
||||
<div class="font-weight-medium">
|
||||
{% if user is not null %}
|
||||
{{ form.vars.data.user.displayName }}
|
||||
{% else %}
|
||||
__DISPLAY__
|
||||
{% endif %}
|
||||
{{ form_widget(form.user) }}
|
||||
{{ form_errors(form.user) }}
|
||||
</div>
|
||||
{% if user is not null %}
|
||||
<div class="text-muted">{{- form.vars.data.user.title -}}</div>
|
||||
{% else %}
|
||||
<div class="text-muted">__TITLE__</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<a href="#" class="btn btn-icon remove-member">{{ icon('trash', true) }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock team_member_widget %}
|
||||
|
||||
{% block date_widget -%}
|
||||
{% set format = locale_format('date') %}
|
||||
{% set jsFormat = format|js_format %}
|
||||
{% set attr = attr|merge({'pattern': format|pattern, 'autocomplete': 'off', 'data-datepicker': 'on', 'data-format': jsFormat, 'placeholder': jsFormat}) -%}
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<a href="#" data-form-widget="date-now" data-format="{{ jsFormat }}" data-target="{{ id }}">{{ icon('calendar') }}</a>
|
||||
</div>
|
||||
{{- block('form_widget_simple') -}}
|
||||
</div>
|
||||
{%- endblock date_widget %}
|
||||
|
||||
{% block time_widget -%}
|
||||
{%- set format = locale_format('time') -%}
|
||||
{%- set jsFormat = format|js_format -%}
|
||||
{%- set attr = attr|merge({'pattern': format|pattern, 'autocomplete': 'off', 'data-timepicker': 'on', 'data-format': jsFormat, 'placeholder': jsFormat}) -%}
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<a href="#" data-form-widget="date-now" data-format="{{ jsFormat }}" data-target="{{ id }}">{{ icon('calendar') }}</a>
|
||||
</div>
|
||||
{{ block('form_widget_simple') }}
|
||||
{% set time_presets = form_time_presets(app.user.timezone, format) %}
|
||||
{% if time_presets|length > 0 and form.vars.disabled is same as (false) %}
|
||||
<button type="button" class="btn dropdown-toggle btn-duration-preset" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
|
||||
<div class="dropdown-menu dropdown-menu-end pre-scrollable">
|
||||
<div class="dropdown-menu-columns">
|
||||
{% for index in [0, 1, 2, 3] %}
|
||||
<div class="dropdown-menu-column" style="min-width: 4rem">
|
||||
{% for value in time_presets %}
|
||||
{% if loop.index0 % 4 == index %}
|
||||
<a class="dropdown-item justify-content-center" href="#" data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ value }}" data-event="change">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- endblock time_widget %}
|
||||
|
||||
{% block daterange_widget %}
|
||||
{% set format = locale_format('date')|js_format %}
|
||||
{% set attr = attr|merge({'data-daterangepicker': 'on', 'autocomplete': 'off', 'data-format': format, 'placeholder': format ~ attr['data-separator'] ~ format}) -%}
|
||||
<div class="input-group">
|
||||
{% if ranges is defined %}
|
||||
<button type="button" class="btn dropdown-toggle btn-duration-preset" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
|
||||
<div class="dropdown-menu dropdown-menu-start pre-scrollable">
|
||||
{% for title, range in ranges %}
|
||||
<a class="dropdown-item" href="#" data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{% if range[0] is not null %}{{ range[0]|format_date('short', rangeFormat) }}{{ attr['data-separator'] }}{{ range[1]|format_date('short', rangeFormat) }}{% endif %}" data-event="change keyup">{{ ('daterangepicker.' ~ title)|trans({}, 'daterangepicker') }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ block('form_widget_simple') }}
|
||||
</div>
|
||||
{% endblock daterange_widget %}
|
||||
|
||||
{% block duration_widget %}
|
||||
<div class="duration-widget">
|
||||
{% if (form.vars.duration_presets is defined and form.vars.duration_presets is not empty) and (form.vars.disabled is same as (false)) %}
|
||||
<div class="input-group">
|
||||
<button type="button" class="btn dropdown-toggle btn-duration-preset" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
|
||||
<div class="dropdown-menu dropdown-menu-start pre-scrollable">
|
||||
<div class="dropdown-menu-columns">
|
||||
{% for index in [0, 1, 2, 3] %}
|
||||
<div class="dropdown-menu-column" style="min-width: 4rem">
|
||||
{% for value in form.vars.duration_presets %}
|
||||
{% if loop.index0 % 4 == index %}
|
||||
<a class="dropdown-item justify-content-center" href="#" data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ value }}" data-event="change">{{ value }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{{ block('form_widget_simple') }}
|
||||
{% if form.vars.toggle is defined and form.vars.toggle %}
|
||||
<div class="input-group-text add-on">
|
||||
<a href="#" id="{{ form.vars.id }}_toggle" class="text-success">{{ icon('link') }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ block('form_widget_simple') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock duration_widget %}
|
||||
|
||||
{% block date_time_widget -%}
|
||||
<div {{ block('widget_container_attributes') }}>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
{{- form_widget(form.date, { datetime: true } ) -}}
|
||||
{{- form_errors(form.date) -}}
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
{{- form_widget(form.time, { datetime: true } ) -}}
|
||||
{{- form_errors(form.time) -}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{%- endblock date_time_widget %}
|
||||
|
||||
{% block text_widget -%}
|
||||
{% if icon is not empty %}
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<i class="{{ icon|icon(false, icon) }}"></i>
|
||||
</div>
|
||||
{{ block('form_widget_simple') }}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ block('form_widget_simple') }}
|
||||
{% endif %}
|
||||
{%- endblock text_widget %}
|
||||
|
||||
{% block mail_widget -%}
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
{{ icon('mail') }}
|
||||
</div>
|
||||
{{ block('email_widget') }}
|
||||
</div>
|
||||
{%- endblock mail_widget %}
|
||||
|
||||
{# user password fields, search for "secret" block_prefix #}
|
||||
{% block secret_widget -%}
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
{{ icon('password') }}
|
||||
</div>
|
||||
{{ block('password_widget') }}
|
||||
</div>
|
||||
{%- endblock secret_widget %}
|
||||
|
||||
{% block yearpicker_widget -%}
|
||||
{%- set functionName = 'year_' ~ form.vars.id %}
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-left btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ previousYear|date_short }}"
|
||||
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ previousYear|report_date }}" data-event-bubbles="change">
|
||||
{{ icon('left') }}
|
||||
</a>
|
||||
<a class="btn" href="#" onclick="return false;">
|
||||
<span id="{{ form.vars.id }}_month_name">
|
||||
{% if show_range %}
|
||||
{{ year|date_short }} – {{ nextYear|date_short }}
|
||||
{% else %}
|
||||
{{ year|date_format('Y') }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
<a class="btn btn-right btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ nextYear|date_short }}"
|
||||
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ nextYear|report_date }}" data-event-bubbles="change">
|
||||
{{ icon('right') }}
|
||||
</a>
|
||||
</div>
|
||||
{%- set required = false -%}
|
||||
<input type="hidden" {{ block('widget_attributes') }} value="{{ value }}" />
|
||||
{%- endblock yearpicker_widget %}
|
||||
|
||||
{% block monthpicker_widget -%}
|
||||
{%- set functionName = 'month_' ~ form.vars.id %}
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-left btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ previousMonth|month_name(true) }}"
|
||||
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ previousMonth|report_date }}" data-event-bubbles="change">
|
||||
{{ icon('left') }}
|
||||
</a>
|
||||
<a class="btn" href="#" onclick="return false;">
|
||||
<span id="{{ form.vars.id }}_month_name">{{ month|month_name(true) }}</span>
|
||||
</a>
|
||||
<a class="btn btn-right btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ nextMonth|month_name(true) }}"
|
||||
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ nextMonth|report_date }}" data-event-bubbles="change">
|
||||
{{ icon('right') }}
|
||||
</a>
|
||||
</div>
|
||||
{%- set required = false -%}
|
||||
<input type="hidden" {{ block('widget_attributes') }} value="{{ value }}" />
|
||||
{%- endblock monthpicker_widget %}
|
||||
|
||||
{% block weekpicker_widget -%}
|
||||
{%- set functionName = 'week_' ~ form.vars.id %}
|
||||
<div class="btn-group week-picker-btn-group {{ attr.class ?? '' }}">
|
||||
<a class="btn btn-left btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': previousWeek|date_format('W')}) }}"
|
||||
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ previousWeek|report_date }}" data-event-bubbles="change">
|
||||
{{ icon('left') }}
|
||||
</a>
|
||||
<a class="btn btn-default" href="#" onclick="return false;">
|
||||
<span id="{{ form.vars.id }}_week_number" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': week|date_format('W')}) }}">
|
||||
{{ week|month_name(true) }}
|
||||
–
|
||||
{{ 'stats.workingTimeWeekShort'|trans({'%week%': week|date_format('W')}) }}
|
||||
</span>
|
||||
</a>
|
||||
<a class="btn btn-right btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': nextWeek|date_format('W')}) }}"
|
||||
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ nextWeek|report_date }}" data-event-bubbles="change">
|
||||
{{ icon('right') }}
|
||||
</a>
|
||||
</div>
|
||||
{%- set required = false -%}
|
||||
<input type="hidden" {{ block('widget_attributes') }} value="{{ value }}" />
|
||||
{%- endblock weekpicker_widget %}
|
||||
|
||||
{% block tags_widget %}
|
||||
{% if form.vars.choices is defined %}
|
||||
{{ block('choice_widget_collapsed') }}
|
||||
{% else %}
|
||||
{{ block('form_widget_simple') }}
|
||||
{% endif %}
|
||||
{% endblock tags_widget %}
|
||||
|
||||
{% block report_sum_widget %}
|
||||
<div class="dropdown"{% if form.vars.choices|length <= 1 %} style="display: none"{% endif %}>
|
||||
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ icon('display', true) }}
|
||||
</button>
|
||||
<ul class="dropdown-menu checkbox-menu">
|
||||
{% for option in form.children %}
|
||||
<li class="dropdown-item">
|
||||
{{ form_widget(option) }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock report_sum_widget %}
|
||||
|
||||
{# customer phone/fax, search for "phone" block_prefix #}
|
||||
{% block phone_widget -%}
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
{{ icon(attr.icon ?? 'phone') }}
|
||||
</div>
|
||||
{{ block('tel_widget') }}
|
||||
</div>
|
||||
{%- endblock phone_widget %}
|
||||
|
||||
{# customer homepage, search for "homepage" block_prefix #}
|
||||
{% block homepage_widget -%}
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
https://
|
||||
</div>
|
||||
{{ block('url_widget') }}
|
||||
</div>
|
||||
{%- endblock homepage_widget %}
|
||||
|
||||
{%- block form_errors -%}
|
||||
{%- if errors|length > 0 -%}
|
||||
{%- for error in errors -%}
|
||||
<div class="invalid-feedback d-block{% if form is rootform and loop.last %} mb-3{% endif %}">{{ error.message }}</div>
|
||||
{%- endfor -%}
|
||||
{%- endif %}
|
||||
{%- endblock form_errors %}
|
||||
|
||||
{%- block meta_fields_collection_row -%}
|
||||
<div{% with {attr: row_attr|merge({class: (row_attr.class|default(form.parent.vars.name ~ '_row_' ~ form.vars.name))|trim})} %}{{ block('attributes') }}{% endwith %}>
|
||||
{{- form_widget(form) -}}
|
||||
</div>
|
||||
{%- endblock meta_fields_collection_row %}
|
||||
|
||||
{%- block meta_fields_collection_widget -%}
|
||||
{% for meta in form|sort((a, b) => a.vars.data.order <=> b.vars.data.order) %}
|
||||
{{ form_row(meta.value, {'row_attr': {'class': 'mb-3 ' ~ form.parent.vars.name ~ '_row_' ~ form.vars.name ~ '_' ~ meta.vars.name}}) }}
|
||||
{% endfor %}
|
||||
{%- endblock meta_fields_collection_widget %}
|
||||
2
templates/form/horizontal.html.twig
Normal file
2
templates/form/horizontal.html.twig
Normal file
@@ -0,0 +1,2 @@
|
||||
{% extends '@theme/layout/form-theme-horizontal.html.twig' %}
|
||||
{% use 'form/blocks.html.twig' %}
|
||||
@@ -1,170 +0,0 @@
|
||||
{% extends "@AdminLTE/layout/form-theme.html.twig" %}
|
||||
|
||||
{# Adds the help icon, including a link to the documentation #}
|
||||
{% block form_label %}
|
||||
{% if form.vars.docu_chapter is defined and form.vars.docu_chapter is not empty %}
|
||||
<a href="{{ form.vars.docu_chapter|docu_link }}" target="_blank"><i class="{{ 'help'|icon }}"></i></a>
|
||||
{% endif %}
|
||||
{{ parent() }}
|
||||
{% endblock form_label %}
|
||||
|
||||
{% block quick_entry_week_row %}
|
||||
<tr{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' form-group qe-entry-week-row' ~ (not valid ? ' has-error'))|trim})} %}{{ block('attributes') }}{% endwith %}>
|
||||
<td>
|
||||
{{ form_row(form.project) }}
|
||||
</td>
|
||||
<td>
|
||||
{{ form_row(form.activity) }}
|
||||
</td>
|
||||
{% for timesheet in form.timesheets %}
|
||||
<td class="text-center{% if timesheet.vars.data.begin is weekend %} weekend{% endif %}{% if timesheet.vars.data.begin is today %} today{% endif %}">
|
||||
{{ form_row(timesheet) }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td class="text-nowrap text-center total qe-totals-row"></td>
|
||||
</tr>
|
||||
{% endblock %}
|
||||
|
||||
{% block _team_edit_form_members_entry_user_widget %}
|
||||
{# this will convert the select box into a hidden field, which are exchangable from an HTML perspective #}
|
||||
{%- set type = 'hidden' -%}
|
||||
{{ block('form_widget_simple') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block team_member_widget %}
|
||||
<div class="row">
|
||||
<div class="col-xs-7">
|
||||
<div class="checkbox">
|
||||
<button class="btn btn-default btn-xs" onclick="jQuery(this).parent().parent().parent().remove();return false;"><i class="{{ 'trash'|icon }}"></i></button>
|
||||
{% if form.vars.data is not null and form.vars.data.user is not null %}
|
||||
{{ form.vars.data.user.displayName }}
|
||||
{% else %}
|
||||
__USERNAME__
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ form_widget(form.user) }}
|
||||
{{ form_errors(form.user) }}
|
||||
</div>
|
||||
<div class="col-xs-5">
|
||||
{{ form_widget(form.teamlead) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock team_member_widget %}
|
||||
|
||||
{% block daterange_widget %}
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="far fa-calendar-alt"></i>
|
||||
</div>
|
||||
|
||||
{% set attr = attr|merge({'data-daterangepickerenable':'on'}) %}
|
||||
|
||||
{{ block('form_widget_simple') }}
|
||||
</div>
|
||||
{% endblock daterange_widget %}
|
||||
|
||||
{% block duration_widget %}
|
||||
{% if (form.vars.duration_presets is defined and form.vars.duration_presets is not empty) and (form.vars.disabled is same as (false)) %}
|
||||
<div class="input-group">
|
||||
{{ block('form_widget_simple') }}
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="caret"></span></button>
|
||||
<ul class="dropdown-menu dropdown-menu-right pre-scrollable">
|
||||
{% for value in form.vars.duration_presets %}
|
||||
<li class="text-center">
|
||||
<a href="#" onclick="jQuery('#{{ form.vars.id }}').val('{{ value }}').trigger('change');return false;">{{ value }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ block('form_widget_simple') }}
|
||||
{% endif %}
|
||||
{% endblock duration_widget %}
|
||||
|
||||
{% block datetime_widget -%}
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<a href="#" onclick="if (!jQuery('#{{ id }}').is(':disabled')) { jQuery('#{{ id }}').val(moment().format('{{ attr['data-format'] }}')).change(); }return false;"><i class="{{ 'calendar'|icon }}"></i></a>
|
||||
</div>
|
||||
{{ block('form_widget_simple') }}
|
||||
</div>
|
||||
{%- endblock datetime_widget %}
|
||||
|
||||
{% block date_widget -%}
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<a href="#" onclick="if (!jQuery('#{{ id }}').is(':disabled')) { jQuery('#{{ id }}').val(moment().format('{{ attr['data-format'] }}')).change(); }return false;"><i class="{{ 'calendar'|icon }}"></i></a>
|
||||
</div>
|
||||
{{ block('form_widget_simple') }}
|
||||
</div>
|
||||
{%- endblock date_widget %}
|
||||
|
||||
{% block text_widget -%}
|
||||
{% if icon is not empty %}
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="{{ icon|icon }}"></i>
|
||||
</div>
|
||||
{{ block('form_widget_simple') }}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ block('form_widget_simple') }}
|
||||
{% endif %}
|
||||
{%- endblock text_widget %}
|
||||
|
||||
{% block yearpicker_widget -%}
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default btn-left" href="#" onclick="jQuery('#{{ form.vars.id }}').val('{{ previousYear|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ previousYear|date_short }}">
|
||||
<i class="{{ 'left'|icon }}"></i>
|
||||
</a>
|
||||
<a class="btn btn-default" href="#" onclick="return false;">
|
||||
<span id="{{ form.vars.id }}_month_name">
|
||||
{% if show_range %}
|
||||
{{ year|date_short }} – {{ nextYear|date_short }}
|
||||
{% else %}
|
||||
{{ year|date_format('Y') }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn-right" href="#" onclick="jQuery('#{{ form.vars.id }}').val('{{ nextYear|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ nextYear|date_short }}">
|
||||
<i class="{{ 'right'|icon }}"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{ block('hidden_widget') }}
|
||||
{%- endblock yearpicker_widget %}
|
||||
|
||||
{% block monthpicker_widget -%}
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default btn-left" href="#" onclick="jQuery('#{{ form.vars.id }}').val('{{ previousMonth|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ previousMonth|month_name(true) }}">
|
||||
<i class="{{ 'left'|icon }}"></i>
|
||||
</a>
|
||||
<a class="btn btn-default" href="#" onclick="return false;">
|
||||
<span id="{{ form.vars.id }}_month_name">{{ month|month_name(true) }}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn-right" href="#" onclick="jQuery('#{{ form.vars.id }}').val('{{ nextMonth|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ nextMonth|month_name(true) }}">
|
||||
<i class="{{ 'right'|icon }}"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{ block('hidden_widget') }}
|
||||
{%- endblock monthpicker_widget %}
|
||||
|
||||
{% block weekpicker_widget -%}
|
||||
<div class="btn-group week-picker-btn-group">
|
||||
<a class="btn btn-default btn-left" href="#" onclick="jQuery('#{{ form.vars.id }}').val('{{ previousWeek|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': previousWeek|date_format('W')}) }}">
|
||||
<i class="{{ 'left'|icon }}"></i>
|
||||
</a>
|
||||
<a class="btn btn-default" href="#" onclick="return false;">
|
||||
<span id="{{ form.vars.id }}_week_number" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': week|date_format('W')}) }}">
|
||||
{{ week|month_name(true) }}
|
||||
–
|
||||
{{ 'stats.workingTimeWeekShort'|trans({'%week%': week|date_format('W')}) }}
|
||||
</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn-right" href="#" onclick="jQuery('#{{ form.vars.id }}').val('{{ nextWeek|report_date }}').change()" data-toggle="tooltip" data-placement="top" title="{{ 'stats.workingTimeWeek'|trans({'%week%': nextWeek|date_format('W')}) }}">
|
||||
<i class="{{ 'right'|icon }}"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{ block('hidden_widget') }}
|
||||
{%- endblock weekpicker_widget %}
|
||||
@@ -1,16 +0,0 @@
|
||||
{% extends "@AdminLTE/layout/form-theme-horizontal.html.twig" %}
|
||||
{#
|
||||
{% block widget_attributes %}
|
||||
{% if type is not defined or type not in ['file', 'hidden'] %}
|
||||
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' input-sm')|trim}) -%}
|
||||
{% endif %}
|
||||
{{- parent() -}}
|
||||
{% endblock widget_attributes %}
|
||||
#}
|
||||
{% block form_label_class -%}
|
||||
col-sm-3 col-xs-4
|
||||
{%- endblock form_label_class %}
|
||||
|
||||
{% block form_group_class -%}
|
||||
col-sm-9 col-xs-8
|
||||
{%- endblock form_group_class %}
|
||||
16
templates/form/search.html.twig
Normal file
16
templates/form/search.html.twig
Normal file
@@ -0,0 +1,16 @@
|
||||
{% extends '@theme/layout/form-theme-horizontal.html.twig' %}
|
||||
|
||||
{% block form_label_class -%}
|
||||
col-sm-4 col-xs-5
|
||||
{%- endblock form_label_class %}
|
||||
|
||||
{% block form_group_class -%}
|
||||
col-sm-8 col-xs-7
|
||||
{%- endblock form_group_class %}
|
||||
|
||||
{% block form_row -%}
|
||||
{%- if expanded is not defined or not expanded -%}
|
||||
{%- set row_class = 'mb-2' -%}
|
||||
{% endif %}
|
||||
{{ parent() }}
|
||||
{%- endblock form_row %}
|
||||
2
templates/form/vertical.html.twig
Normal file
2
templates/form/vertical.html.twig
Normal file
@@ -0,0 +1,2 @@
|
||||
{% extends '@theme/layout/form-theme.html.twig' %}
|
||||
{% use 'form/blocks.html.twig' %}
|
||||
@@ -1,8 +1,3 @@
|
||||
{% macro invoices(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'invoices', view) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro invoice(invoice, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
@@ -10,24 +5,6 @@
|
||||
{{ widgets.table_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro invoice_listing(view, query) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'invoice_details', view, {'query': query}) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro invoice_upload(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'invoice_upload', view) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro invoice_templates(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'invoice_templates', view) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro invoice_template(template, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'invoice_template', view, {'template': template, 'token': csrf_token('invoice.delete_template')}) %}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "invoice/actions.html.twig" as actions %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block page_title %}{{ 'label.invoice_renderer'|trans({}, 'invoice-renderer') }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.invoice_upload('index') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if can_upload and form is not null %}
|
||||
{% form_theme form '@AdminLTE/layout/form-theme-horizontal.html.twig' %}
|
||||
{% set formOptions = {
|
||||
'title': 'upload'|trans,
|
||||
'form': form,
|
||||
'back': false,
|
||||
'reset': false
|
||||
} %}
|
||||
|
||||
{% embed 'default/_form.html.twig' with formOptions %}
|
||||
{% block form_body %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% form_theme form 'form/horizontal.html.twig' %}
|
||||
{% block box_title %}
|
||||
{{ 'upload'|trans }}
|
||||
{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.document) }}
|
||||
{{ form_widget(form) }}
|
||||
<p>
|
||||
<a href="https://github.com/kimai/invoice-templates" target="_blank">{{ 'download_invoice_renderer'|trans({}, 'invoice-renderer') }}</a>
|
||||
</p>
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
{% block box_footer %}
|
||||
<button type="submit" class="btn btn-primary">{{ 'action.save'|trans }}</button>
|
||||
<a href="{{ path('admin_invoice_template') }}" class="btn btn-link">{{ 'back'|trans({}, 'actions') }}</a>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% elseif upload_error is not null %}
|
||||
@@ -26,21 +26,21 @@
|
||||
{% endif %}
|
||||
|
||||
{% if documents|length > 0 %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'documents': documents} %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {'documents': documents} %}
|
||||
{% import "invoice/actions.html.twig" as actions %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'label.invoice_renderer'|trans({}, 'invoice-renderer') }}{% endblock %}
|
||||
{% block box_title %}{{ 'invoice_renderer'|trans({}, 'invoice-renderer') }}{% endblock %}
|
||||
{% block box_attributes %}
|
||||
id="invoice_document_list"
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_body %}
|
||||
<table class="table table-hover dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'file'|trans }}</th>
|
||||
<th>{{ 'updated_at'|trans }}</th>
|
||||
<th>{{ 'label.template'|trans }}</th>
|
||||
<th>{{ 'template'|trans }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -1,235 +1,216 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "invoice/actions.html.twig" as actions %}
|
||||
|
||||
{% set totalDuration = 0 %}
|
||||
{% set totalRates = {} %}
|
||||
|
||||
{% set showEmpty = false %}
|
||||
{% if searched %}
|
||||
{% set showEmpty = true %}
|
||||
{% for model in models %}
|
||||
{% if showEmpty %}
|
||||
{% set showEmpty = model.calculator is empty or model.calculator.entries is empty %}
|
||||
{% endif %}
|
||||
{% set totalDuration = totalDuration + model.calculator.timeWorked %}
|
||||
{% set customerCurrency = model.customer.currency %}
|
||||
{% if totalRates[customerCurrency] is not defined %}
|
||||
{% set totalRates = totalRates|merge({(customerCurrency): 0.00}) %}
|
||||
{% endif %}
|
||||
{% set totalRates = totalRates|merge({(customerCurrency): totalRates[customerCurrency] + model.calculator.total}) %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% set columns = {
|
||||
'date': {'class': 'alwaysVisible w-min', 'orderBy': false},
|
||||
'project': {'class': 'hidden-xs hidden-sm', 'orderBy': false},
|
||||
'description': {'class': 'hidden-xs hidden-sm hidden', 'orderBy': false},
|
||||
'user': {'class': 'hidden-xs hidden-sm w-min', 'orderBy': false},
|
||||
'unit_price': {'class': 'hidden-xs text-right w-min', 'orderBy': false},
|
||||
'amount': {'class': 'text-right w-min', 'orderBy': false},
|
||||
'duration': {'class': 'hidden-xs text-right w-min', 'orderBy': false},
|
||||
'total_rate': {'class': 'text-right alwaysVisible w-min', 'orderBy': false},
|
||||
'project': {'class': 'd-none d-md-table-cell', 'orderBy': false},
|
||||
'description': {'class': 'd-none d-xl-table-cell', 'orderBy': false},
|
||||
'user': {'class': 'd-none d-xl-table-cell w-min', 'orderBy': false},
|
||||
'unit_price': {'class': 'd-none d-sm-table-cell text-end w-min', 'orderBy': false},
|
||||
'amount': {'class': 'd-none d-sm-table-cell text-end w-min', 'orderBy': false},
|
||||
'duration': {'class': 'text-end w-min', 'orderBy': false},
|
||||
'total_rate': {'class': 'text-end alwaysVisible w-min', 'orderBy': false},
|
||||
} %}
|
||||
|
||||
{% set tableName = 'invoice' %}
|
||||
{% set tableName = 'invoice_create' %}
|
||||
|
||||
{% block page_title %}{{ 'invoices'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.invoices('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% block status %}
|
||||
{% from "macros/status.html.twig" import status_duration, status_money %}
|
||||
{{ status_duration(totalDuration|duration) }}
|
||||
{% for totalCurrency, totalRate in totalRates %}
|
||||
{{ status_money(totalRate|money(totalCurrency)) }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if is_granted('create_invoice') %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/search.html.twig" as search %}
|
||||
{% form_theme form '@AdminLTE/layout/form-theme-horizontal.html.twig' %}
|
||||
{% block box_title %}{{ 'invoice.filter'|trans }}{% endblock %}
|
||||
{% block box_before %}{{ form_start(form) }}{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ form_errors(form) }}
|
||||
{% if form.searchTerm is defined %}
|
||||
{{ form_row(form.searchTerm, {'row_attr': {'class': 'invoice_search_form_row_searchTerm'}}) }}
|
||||
{% endif %}
|
||||
{{ form_row(form.daterange, {'row_attr': {'class': 'invoice_search_form_row_daterange'}}) }}
|
||||
{{ form_row(form.customers, {'row_attr': {'class': 'invoice_search_form_row_customers'}}) }}
|
||||
{{ form_row(form.projects, {'row_attr': {'class': 'invoice_search_form_row_projects'}}) }}
|
||||
{% if form.activities is defined %}
|
||||
{{ form_row(form.activities, {'row_attr': {'class': 'invoice_search_form_row_activities'}}) }}
|
||||
{% endif %}
|
||||
{% if form.tags is defined %}
|
||||
{{ form_row(form.tags, {'row_attr': {'class': 'invoice_search_form_row_tags'}}) }}
|
||||
{% endif %}
|
||||
{% if form.users is defined %}
|
||||
{{ form_row(form.users, {'row_attr': {'class': 'invoice_search_form_row_users'}}) }}
|
||||
{% endif %}
|
||||
{% if form.teams is defined %}
|
||||
{{ form_row(form.teams, {'row_attr': {'class': 'invoice_search_form_row_teams'}}) }}
|
||||
{% endif %}
|
||||
{% if form.exported is defined %}
|
||||
{{ form_row(form.exported, {'row_attr': {'class': 'invoice_search_form_row_exported'}}) }}
|
||||
{% endif %}
|
||||
{{ form_row(form.template, {'row_attr': {'class': 'invoice_search_form_row_template'}}) }}
|
||||
{% if form.markAsExported is defined %}
|
||||
{{ form_row(form.markAsExported, {'row_attr': {'class': 'invoice_search_form_row_markAsExported'}}) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_footer%}
|
||||
{{ search.searchButton(form) }}
|
||||
{% endblock %}
|
||||
{% block box_after %}{{ form_end(form) }}{% endblock %}
|
||||
{% endembed %}
|
||||
{% else %}
|
||||
{{ widgets.callout('danger', 'http_error_403.suggestion'|trans({}, 'exceptions')) }}
|
||||
{% endif %}
|
||||
|
||||
{% set totalDuration = 0 %}
|
||||
{% set totalRates = {} %}
|
||||
|
||||
{% if searched %}
|
||||
{% set showEmpty = true %}
|
||||
{% for model in models %}
|
||||
{% if showEmpty %}
|
||||
{% set showEmpty = model.calculator is empty or model.calculator.entries is empty %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% import "macros/search.html.twig" as search %}
|
||||
{% form_theme form 'form/horizontal.html.twig' %}
|
||||
{% block box_title %}{{ 'invoice.filter'|trans }}{% endblock %}
|
||||
{% block box_before %}{{ form_start(form) }}{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ form_errors(form) }}
|
||||
{% if form.searchTerm is defined %}
|
||||
{{ form_row(form.searchTerm, {'row_attr': {'class': 'mb-3 invoice_search_form_row_searchTerm'}}) }}
|
||||
{% endif %}
|
||||
{% set totalDuration = totalDuration + model.calculator.timeWorked %}
|
||||
{% set customerCurrency = model.customer.currency %}
|
||||
{% if totalRates[customerCurrency] is not defined %}
|
||||
{% set totalRates = totalRates|merge({(customerCurrency): 0.00}) %}
|
||||
{{ form_row(form.daterange, {'row_attr': {'class': 'mb-3 invoice_search_form_row_daterange'}}) }}
|
||||
{{ form_row(form.customers, {'row_attr': {'class': 'mb-3 invoice_search_form_row_customers'}}) }}
|
||||
{{ form_row(form.projects, {'row_attr': {'class': 'mb-3 invoice_search_form_row_projects'}}) }}
|
||||
{% if form.activities is defined %}
|
||||
{{ form_row(form.activities, {'row_attr': {'class': 'mb-3 invoice_search_form_row_activities'}}) }}
|
||||
{% endif %}
|
||||
{% set totalRates = totalRates|merge({(customerCurrency): totalRates[customerCurrency] + model.calculator.total}) %}
|
||||
{% endfor %}
|
||||
{% if showEmpty %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% endif %}
|
||||
{% if form.tags is defined %}
|
||||
{{ form_row(form.tags, {'row_attr': {'class': 'mb-3 invoice_search_form_row_tags'}}) }}
|
||||
{% endif %}
|
||||
{% if form.users is defined %}
|
||||
{{ form_row(form.users, {'row_attr': {'class': 'mb-3 invoice_search_form_row_users'}}) }}
|
||||
{% endif %}
|
||||
{% if form.teams is defined %}
|
||||
{{ form_row(form.teams, {'row_attr': {'class': 'mb-3 invoice_search_form_row_teams'}}) }}
|
||||
{% endif %}
|
||||
{% if form.exported is defined %}
|
||||
{{ form_row(form.exported, {'row_attr': {'class': 'mb-3 invoice_search_form_row_exported'}}) }}
|
||||
{% endif %}
|
||||
<div class="d-none">
|
||||
{{ form_row(form.template, {'row_attr': {'class': 'mb-3 invoice_search_form_row_template'}}) }}
|
||||
{{ form_row(form.invoiceDate, {'row_attr': {'class': 'mb-3 invoice_search_form_row_invoiceDate'}}) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block box_footer%}
|
||||
{{ search.searchButton(form) }}
|
||||
{% endblock %}
|
||||
{% block box_after %}{{ form_end(form) }}{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
{% if showEmpty %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% endif %}
|
||||
|
||||
{% if models|length > 0 %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import '@AdminLTE/Macros/buttons.html.twig' as button %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% block box_title %}
|
||||
{{ 'button.preview'|trans }}: {{ 'invoices'|trans }}
|
||||
{{ widgets.label(totalDuration|duration, 'gray') }}
|
||||
{% for totalCurrency, totalRate in totalRates %}
|
||||
{{ widgets.label(totalRate|money(totalCurrency), 'gray') }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_footer %}
|
||||
<a href="#" onclick="return saveAllInvoices(this);" class="btn btn-success" id="create_all_invoices">
|
||||
{{ 'action.save_all'|trans }}
|
||||
</a>
|
||||
{{ 'preview'|trans }}
|
||||
{% endblock %}
|
||||
{% block box_attributes %}id="invoice_customer_forms"{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_body %}
|
||||
<table class="table table-hover dataTable">
|
||||
<table class="table dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th class="w-min text-center actions"></th>
|
||||
<th class="w-min text-right hidden-xs">{{ 'label.duration'|trans }}</th>
|
||||
<th class="w-min text-right hidden-xs">{{ 'label.total_rate'|trans }}</th>
|
||||
<th class="d-sm-table-cell d-none"></th>
|
||||
<th>{{ 'customer'|trans }}</th>
|
||||
<th class="w-min text-end d-sm-table-cell d-none">{{ 'duration'|trans }}</th>
|
||||
<th class="w-min text-end d-sm-table-cell d-none">{{ 'total_rate'|trans }}</th>
|
||||
<th class="w-min text-center actions alwaysVisible"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for model in models %}
|
||||
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||
{% set isDecimal = true %}
|
||||
{% set currency = model.currency %}
|
||||
{% set isEmptyModel = model.calculator is empty or model.calculator.entries is empty %}
|
||||
{% set customerForm = forms[loop.index0] %}
|
||||
<tr>
|
||||
<td class="w-min d-sm-table-cell d-none">
|
||||
{% if not isEmptyModel %}
|
||||
{{ widgets.card_tool_button('collapse', {'onclick': "this.parentElement.parentElement.nextElementSibling.classList.toggle('d-none');return false;"}) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if is_granted('view', model.customer) %}
|
||||
<a class="link-black" href="{{ path('customer_details', {id: model.customer.id}) }}">{{ widgets.label_customer(model.customer) }}</a>
|
||||
<a class="text-reset" href="{{ path('customer_details', {id: model.customer.id}) }}">{{ widgets.label_customer(model.customer) }}</a>
|
||||
{% else %}
|
||||
{{ widgets.label_customer(model.customer) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="w-min text-center">
|
||||
{{ widgets.action_button('print', {'url': '#', 'onclick': 'return singleInvoice(this, true)', 'title': 'button.preview'|trans, 'target': '_blank', 'class': 'btn btn-sm', 'attr': {'data-customer': model.customer.id, 'data-template': model.template.id, 'data-href': path('invoice_preview', {'customer': model.customer.id, 'token': csrf_token('invoice.preview')})}}) }}
|
||||
{{ widgets.action_button('save', {'url': '#', 'onclick': 'return singleInvoice(this, false)', 'title': 'action.save'|trans, 'class': 'btn btn-sm', 'attr': {'data-customer': model.customer.id, 'data-template': model.template.id, 'data-href': path('invoice_create', {'customer': model.customer.id, 'template': model.template.id, 'token': csrf_token('invoice.create')})}}, 'success') }}
|
||||
</td>
|
||||
<td class="w-min text-right hidden-xs">
|
||||
<td class="w-min text-end d-sm-table-cell d-none">
|
||||
{{ model.calculator.timeWorked|duration(isDecimal) }}
|
||||
</td>
|
||||
<td class="w-min text-right hidden-xs">
|
||||
<span title="{{ 'label.vat'|trans }}: {{ model.calculator.tax|money(currency) }}" data-toggle="tooltip">{{ model.calculator.total|money(currency) }}</span>
|
||||
<td class="w-min text-end d-sm-table-cell d-none">
|
||||
<span title="{{ 'vat'|trans }}: {{ model.calculator.tax|money(currency) }}" data-toggle="tooltip">{{ model.calculator.total|money(currency) }}</span>
|
||||
</td>
|
||||
<td class="w-min text-center actions alwaysVisible">
|
||||
{% for attrs in [{'class': 'd-inline-flex d-md-none'}, {'icon': false, 'class': 'd-none d-md-inline-flex'}] %}
|
||||
{{ widgets.action_button('print', {'url': '#', 'onclick': 'return singleInvoice(this, true)', 'title': 'preview'|trans, 'target': '_blank', 'attr': {'data-customer': model.customer.id, 'data-href': path('invoice_preview', {'customer': model.customer.id, 'token': csrf_token('invoice.preview')})}}|merge(attrs)) }}
|
||||
{{ widgets.action_button('invoice', {'url': '#', 'onclick': 'return singleInvoice(this, false)', 'title': 'action.save'|trans, 'attr': {'data-customer': model.customer.id, 'data-href': path('invoice_create', {'customer': model.customer.id, 'token': csrf_token('invoice.create')})}}|merge(attrs), 'success') }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="d-none">
|
||||
<td colspan="5" class="p-2">
|
||||
{% if not isEmptyModel %}
|
||||
{% form_theme customerForm 'form/horizontal.html.twig' %}
|
||||
{{ form_start(customerForm) }}
|
||||
{{ form_rest(customerForm) }}
|
||||
{{ form_end(customerForm) }}
|
||||
{% set isDecimal = true %}
|
||||
{% set entries = model.calculator.entries %}
|
||||
{% set currency = model.currency %}
|
||||
{% set itemsAmount = entries|length %}
|
||||
{{ tables.datatable_header(tableName, columns, model.query, {'boxClass': ''}) }}
|
||||
{% if limit_preview %}
|
||||
{% set entries = entries|slice(0, 100) %}
|
||||
{% endif %}
|
||||
{% for entry in entries %}
|
||||
{% set amount = entry.amount %}
|
||||
{% set duration = entry.duration|duration(isDecimal) %}
|
||||
{% set rate = 0 %}
|
||||
{% if entry.fixedRate is not null %}
|
||||
{% set rate = entry.fixedRate %}
|
||||
{% elseif entry.hourlyRate is not null %}
|
||||
{% set rate = entry.hourlyRate %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">{{ entry.begin|date_short }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">
|
||||
{% if is_granted('view', entry.project) %}
|
||||
<a class="text-reset" href="{{ path('project_details', {id: entry.project.id}) }}">{{ widgets.label_project(entry.project) }}</a>
|
||||
{% else %}
|
||||
{{ widgets.label_project(entry.project) }}
|
||||
{% endif %}
|
||||
{% if entry.activity is not null %}
|
||||
<br>
|
||||
<small>
|
||||
{% if is_granted('view', entry.activity) %}
|
||||
<a class="text-reset" href="{{ path('activity_details', {id: entry.activity.id}) }}">{{ widgets.label_activity(entry.activity) }}</a>
|
||||
{% else %}
|
||||
{{ widgets.label_activity(entry.activity) }}
|
||||
{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }} timesheet-description">
|
||||
{% if entry.description is not empty %}
|
||||
{{ entry.description|desc2html }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.label_user(entry.user) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'unit_price') }}">{{ rate|money(currency) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'amount') }}">{{ amount }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}" data-duration="{{ entry.duration }}">{{ duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'total_rate') }}">{{ entry.rate|money(currency) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if limit_preview and itemsAmount > 100 %}
|
||||
<tr class="warning">
|
||||
<td colspan="8">» {{ 'preview.skipped_rows'|trans({'%rows%': (itemsAmount - 100)}) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{{ tables.data_table_footer(entries) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="display: none" id="create-token" data-value="{{ csrf_token('invoice.create') }}"></div>
|
||||
<div style="display: none" id="preview-token" data-value="{{ csrf_token('invoice.preview') }}"></div>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
|
||||
{% for model in models %}
|
||||
{% set isEmptyModel = model.calculator is empty or model.calculator.entries is empty %}
|
||||
{% if not isEmptyModel %}
|
||||
{% set customer = model.query.customers[0] %}
|
||||
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||
{% set entries = model.calculator.entries %}
|
||||
{% set currency = model.currency %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% block box_title %}
|
||||
<span id="invoice_preview_details_{{ customer.id }}">{{ widgets.label_customer(customer) }}</span>
|
||||
{{ widgets.label(model.calculator.timeWorked|duration, 'gray') }}
|
||||
{{ widgets.label(model.calculator.total|money(currency), 'gray') }}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}invoice-preview-box no-padding{% endblock %}
|
||||
{% block box_body %}
|
||||
{{ tables.datatable_header(tableName, columns, model.query, {'boxClass': ''}) }}
|
||||
{% set itemsAmount = entries|length %}
|
||||
{% if limit_preview %}
|
||||
{% set entries = entries|slice(0, 100) %}
|
||||
{% endif %}
|
||||
{% for entry in entries %}
|
||||
{% set amount = entry.amount %}
|
||||
{% set duration = entry.duration|duration(isDecimal) %}
|
||||
{% set rate = 0 %}
|
||||
{% if entry.fixedRate is not null %}
|
||||
{% set rate = entry.fixedRate %}
|
||||
{% elseif entry.hourlyRate is not null %}
|
||||
{% set rate = entry.hourlyRate %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">{{ entry.begin|date_short }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">
|
||||
{% if is_granted('view', entry.project) %}
|
||||
<a class="link-black" href="{{ path('project_details', {id: entry.project.id}) }}">{{ widgets.label_project(entry.project) }}</a>
|
||||
{% else %}
|
||||
{{ widgets.label_project(entry.project) }}
|
||||
{% endif %}
|
||||
{% if entry.activity is not null %}
|
||||
<br>
|
||||
<small>
|
||||
{% if is_granted('view', entry.activity) %}
|
||||
<a class="link-black" href="{{ path('activity_details', {id: entry.activity.id}) }}">{{ widgets.label_activity(entry.activity) }}</a>
|
||||
{% else %}
|
||||
{{ widgets.label_activity(entry.activity) }}
|
||||
{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }} timesheet-description">
|
||||
{% if entry.description is not empty %}
|
||||
{{ entry.description|desc2html }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.label_user(entry.user) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'unit_price') }}">{{ rate|money(currency) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'amount') }}">{{ amount }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}" data-duration="{{ entry.duration }}">{{ duration }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'total_rate') }}">{{ entry.rate|money(currency) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if limit_preview and itemsAmount > 100 %}
|
||||
<tr class="warning">
|
||||
<td colspan="8">» {{ 'preview.skipped_rows'|trans({'%rows%': (itemsAmount - 100)}) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr class="summary">
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}"></td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}"></td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}"></td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}"></td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'unit_price') }}"></td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'amount') }}"></td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'duration') }}">{{ model.calculator.timeWorked|duration(isDecimal) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'total_rate') }}">{{ model.calculator.total|money(currency) }}</td>
|
||||
</tr>
|
||||
{{ tables.data_table_footer(entries) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -240,11 +221,13 @@
|
||||
{% set formSelector = 'form#' ~ formId %}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function singleInvoice(link, preview)
|
||||
{
|
||||
const formName = 'customer_' + link.dataset['customer'];
|
||||
const template = document.getElementById(formName + '_template').value;
|
||||
const date = document.getElementById(formName + '_invoiceDate').value;
|
||||
const formPlugin = kimai.getPlugin('form');
|
||||
const overwrites = {'customers[]': link.dataset['customer'], 'template': link.dataset['template']};
|
||||
const overwrites = {'customers[]': link.dataset['customer'], 'template': template, 'invoiceDate': date};
|
||||
const uri = formPlugin.convertFormDataToQueryString(document.getElementById('{{ formId }}'), overwrites);
|
||||
const invoiceUrl = link.dataset['href'] + '?' + uri;
|
||||
|
||||
@@ -261,28 +244,15 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
function saveAllInvoices(link)
|
||||
{
|
||||
const formPlugin = kimai.getPlugin('form');
|
||||
const uri = formPlugin.convertFormDataToQueryString(document.getElementById('{{ formId }}'));
|
||||
|
||||
link.classList.toggle('disabled');
|
||||
link.href = 'javascript: void(0)';
|
||||
|
||||
document.location = '{{ path('invoice') }}?token={{ csrf_token('invoice.create') }}&createInvoice=true&' + uri;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
/** @type CustomEvent event */
|
||||
document.addEventListener('kimai.initialized', function(event) {
|
||||
KimaiReloadPageWidget.create('kimai.systemConfigUpdate', true);
|
||||
|
||||
{% if models|length > 0 %}
|
||||
jQuery('body').on('change', '#{{ form.template.vars.id }}', function(event) {
|
||||
document.getElementById('{{ form.template.vars.id }}').addEventListener('change', function(event) {
|
||||
document.getElementById('{{ form.vars.attr.id }}').submit();
|
||||
});
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block page_title %}{{ 'invoices'|trans }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% 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': invoice.customer.name ~ ' – ' ~ invoice.createdAt|date_short~ ' – ' ~ invoice.total|money(invoice.customer.currency),
|
||||
'form': form,
|
||||
|
||||
@@ -1,76 +1,33 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% extends 'datatable.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/search.html.twig" as search %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "invoice/actions.html.twig" as actions %}
|
||||
{% import "invoice/macros.html.twig" as macros %}
|
||||
|
||||
{% set columns = {
|
||||
'date': {'class': 'alwaysVisible text-nowrap'},
|
||||
'user': {'class': 'hidden-xs hidden-sm text-nowrap hidden', 'orderBy': false},
|
||||
'customer': {'class': 'hidden-xs hidden-sm', 'orderBy': false},
|
||||
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
|
||||
} %}
|
||||
{% for field in metaColumns %}
|
||||
{% set columns = columns|merge({
|
||||
('mf_' ~ field.name): {'title': field.label|trans, 'class': 'hidden-xs hidden-sm mf_' ~ field.name, 'orderBy': false}
|
||||
}) %}
|
||||
{% endfor %}
|
||||
{% set columns = columns|merge({
|
||||
'invoice_number': {'class': 'hidden-xs hidden-sm w-min', 'title': 'invoice.number'|trans, 'orderBy': false},
|
||||
'due_date': {'class': 'hidden-xs w-min', 'title': 'invoice.due_days'|trans, 'orderBy': false},
|
||||
'payment_date': {'class': 'hidden-xs hidden w-min', 'title': 'invoice.payment_date'|trans, 'orderBy': false},
|
||||
'status': {'class': 'alwaysVisible w-min', 'orderBy': false},
|
||||
'subtotal': {'class': 'hidden-xs text-right w-min hidden', 'title': 'invoice.subtotal'|trans, 'orderBy': false},
|
||||
'tax': {'class': 'hidden-xs text-right w-min hidden', 'title': 'invoice.tax'|trans},
|
||||
'total_rate': {'class': 'hidden-xs text-right w-min'},
|
||||
'actions': {'class': 'actions alwaysVisible', 'orderBy': false},
|
||||
}) %}
|
||||
|
||||
{% set tableName = 'invoices' %}
|
||||
|
||||
{% block page_title %}{{ 'invoices'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.invoice_listing('index', query) }}{% endblock %}
|
||||
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if entries is empty %}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
||||
{% for entry in entries %}
|
||||
<tr class="modal-ajax-form open-edit{% if entry.canceled %} warning text-muted{% endif %}" data-href="{{ path('admin_invoice_edit', {'id': entry.id}) }}">
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">{{ entry.createdAt|date_short }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.user_avatar(entry.user) }} {{ widgets.username(entry.user) }}</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 }}</td>
|
||||
{% for field in metaColumns %}
|
||||
<td class="{{ 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, 'invoice_number') }}">{{ widgets.label(entry.invoiceNumber, 'default') }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'due_date') }}">{{ macros.invoice_due_date(entry) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'payment_date') }}">
|
||||
{% if entry.paymentDate and entry.paid %}
|
||||
{{ widgets.label(entry.paymentDate|date_short, 'primary') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'status') }}">{{ macros.invoice_status(entry) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'subtotal') }}">{{ entry.subtotal|money(entry.currency) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'tax') }}">{{ entry.tax|money(entry.currency) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'total_rate') }}">{{ entry.total|money(entry.currency) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">{{ actions.invoice(entry, 'index') }}</td>
|
||||
</tr>
|
||||
{% block datatable_row %}
|
||||
<tr class="modal-ajax-form open-edit{% if entry.canceled %} warning text-muted{% endif %}" data-href="{{ path('admin_invoice_edit', {'id': entry.id}) }}">
|
||||
<td class="{{ tables.class(dataTable, 'avatar') }}">{{ widgets.user_avatar(entry.user) }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'date') }}">{{ entry.createdAt|date_short }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'user') }}">{{ widgets.label_user(entry.user) }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'comment') }}">{{ entry.comment }}</td>
|
||||
{% for field in metaColumns %}
|
||||
<td class="{{ tables.class(dataTable, 'mf_' ~ field.name) }}">
|
||||
{{ tables.datatable_meta_column(entry, field) }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
{{ tables.data_table_footer(entries, 'admin_invoice_list') }}
|
||||
{% endif %}
|
||||
|
||||
<td class="{{ tables.class(dataTable, 'invoice_number') }}">{{ widgets.label(entry.invoiceNumber, 'default') }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'due_date') }}">{{ _self.invoice_due_date(entry) }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'payment_date') }}">
|
||||
{% if entry.paymentDate and entry.paid %}
|
||||
{{ widgets.label(entry.paymentDate|date_short, 'primary') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.class(dataTable, 'status') }}">{{ _self.invoice_status(entry) }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'subtotal') }}">{{ entry.subtotal|money(entry.currency) }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'tax') }}">{{ entry.tax|money(entry.currency) }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'total_rate') }}">{{ entry.total|money(entry.currency) }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'actions') }}">{{ actions.invoice(entry, 'index') }}</td>
|
||||
</tr>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
@@ -82,9 +39,33 @@
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
KimaiReloadPageWidget.create('kimai.invoiceUpdate');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% macro invoice_status(invoice) %}
|
||||
{% from "macros/widgets.html.twig" import label %}
|
||||
{% set overdue = invoice.overdue %}
|
||||
{% if invoice.new and overdue %}
|
||||
{{ label('status.new'|trans, 'danger') }}
|
||||
{% elseif invoice.new %}
|
||||
{{ label('status.new'|trans, 'primary') }}
|
||||
{% elseif invoice.pending and overdue %}
|
||||
{{ label('status.pending'|trans, 'danger') }}
|
||||
{% elseif invoice.pending %}
|
||||
{{ label('status.pending'|trans, 'warning') }}
|
||||
{% elseif invoice.paid %}
|
||||
{{ label('status.paid'|trans, 'success') }}
|
||||
{% elseif invoice.canceled %}
|
||||
{{ label('status.canceled'|trans, 'gray') }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro invoice_due_date(invoice) %}
|
||||
{% from "macros/widgets.html.twig" import label %}
|
||||
{% if invoice.canceled %}
|
||||
{{ label('status.canceled'|trans, 'gray') }}
|
||||
{% elseif invoice.overdue and not invoice.paid %}
|
||||
{{ label(invoice.dueDate|date_short, 'danger') }}
|
||||
{% else %}
|
||||
{{ label(invoice.dueDate|date_short, 'primary') }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
{% macro invoice_status(invoice) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set overdue = invoice.overdue %}
|
||||
{% if invoice.new and overdue %}
|
||||
{{ widgets.label('status.new'|trans, 'danger') }}
|
||||
{% elseif invoice.new %}
|
||||
{{ widgets.label('status.new'|trans, 'primary') }}
|
||||
{% elseif invoice.pending and overdue %}
|
||||
{{ widgets.label('status.pending'|trans, 'danger') }}
|
||||
{% elseif invoice.pending %}
|
||||
{{ widgets.label('status.pending'|trans, 'warning') }}
|
||||
{% elseif invoice.paid %}
|
||||
{{ widgets.label('status.paid'|trans, 'success') }}
|
||||
{% elseif invoice.canceled %}
|
||||
{{ widgets.label('status.canceled'|trans, 'gray') }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro invoice_due_date(invoice) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% if invoice.canceled %}
|
||||
{{ widgets.label('status.canceled'|trans, 'gray') }}
|
||||
{% elseif invoice.overdue and not invoice.paid %}
|
||||
{{ widgets.label(invoice.dueDate|date_short, 'danger') }}
|
||||
{% else %}
|
||||
{{ widgets.label(invoice.dueDate|date_short, 'primary') }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
Binary file not shown.
@@ -1,13 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
{% set fallback = app.request is not null ? app.request.locale : 'en' %}
|
||||
{% set language = model.template.language|default(fallback) %}
|
||||
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||
{% set currency = model.currency %}
|
||||
{%- set fallback = app.request is not null ? app.request.locale : 'en' -%}
|
||||
{%- set language = model.template.language|default(fallback) -%}
|
||||
{%- set isDecimal = true -%}
|
||||
{%- set currency = model.currency -%}
|
||||
{%- set project = model.query.project -%}
|
||||
<html lang="{{ language }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}{{ model.invoiceNumber }}-{{ model.customer.company|default(model.customer.name)|u.snake }}{% endblock %}</title>
|
||||
<style type="text/css">
|
||||
<style>
|
||||
{{ encore_entry_css_source('invoice-pdf')|raw }}
|
||||
</style>
|
||||
</head>
|
||||
@@ -20,7 +21,7 @@
|
||||
{{ model.template.title }}
|
||||
</td>
|
||||
<td class="date text-right">
|
||||
{{ 'label.date'|trans }}: {{ model.invoiceDate|date_short }}
|
||||
{{ 'date'|trans }}: {{ model.invoiceDate|date_short }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -30,10 +31,10 @@
|
||||
<table class="footer">
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ 'label.contact'|trans }}</strong>:
|
||||
<strong>{{ 'contact'|trans }}</strong>:
|
||||
{{ model.template.contact|nl2str(' – ') }}
|
||||
<br>
|
||||
<strong>{{ 'label.invoice_bank_account'|trans }}</strong>:
|
||||
<strong>{{ 'invoice_bank_account'|trans }}</strong>:
|
||||
{{ model.template.paymentDetails|nl2str(' – ') }}
|
||||
</td>
|
||||
<td align="right">
|
||||
@@ -44,7 +45,6 @@
|
||||
</htmlpagefooter>
|
||||
<sethtmlpagefooter page="ALL" value="on" name="footer" show-this-page="1" />
|
||||
mpdf-->
|
||||
<div class="wrapper">
|
||||
<table class="addresses">
|
||||
<tr>
|
||||
<td style="width:60%">
|
||||
@@ -55,7 +55,7 @@ mpdf-->
|
||||
{{ model.customer.address|nl2br }}
|
||||
{% if model.customer.vatId is not empty %}
|
||||
<br>
|
||||
{{ 'label.vat_id'|trans }}: {{ model.customer.vatId }}
|
||||
{{ 'vat_id'|trans }}: {{ model.customer.vatId }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
@@ -66,7 +66,7 @@ mpdf-->
|
||||
{{ model.template.address|trim|nl2br }}
|
||||
{% if model.template.vatId is not empty %}
|
||||
<br>
|
||||
{{ 'label.vat_id'|trans }}:
|
||||
{{ 'vat_id'|trans }}:
|
||||
{{ model.template.vatId }}
|
||||
{% endif %}
|
||||
</td>
|
||||
@@ -83,25 +83,29 @@ mpdf-->
|
||||
|
||||
{% if model.customer.number is not empty %}
|
||||
<br>
|
||||
{{ 'label.number'|trans }}:
|
||||
{{ 'number'|trans }}:
|
||||
{{ model.customer.number }}
|
||||
{% endif %}
|
||||
|
||||
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
|
||||
{% if project is not null and project.orderNumber is not empty %}
|
||||
<br>
|
||||
{{ 'label.orderNumber'|trans }}:
|
||||
{{ model.query.project.orderNumber }}
|
||||
{{ 'orderNumber'|trans }}:
|
||||
{{ project.orderNumber }}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% if model.customer.invoiceText is not empty%}
|
||||
<p>{{ model.customer.invoiceText|md2html }}</p>
|
||||
{% endif %}
|
||||
|
||||
<table class="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="first"><strong>{{ 'label.date'|trans }}</strong></th>
|
||||
<th><strong>{{ 'label.description'|trans }}</strong></th>
|
||||
<th class="text-right"><strong>{{ 'label.unit_price'|trans }}</strong></th>
|
||||
<th class="text-right"><strong>{{ 'label.amount'|trans }}</strong></th>
|
||||
<th class="last text-right"><strong>{{ 'label.total_rate'|trans }}</strong></th>
|
||||
<th class="first"><strong>{{ 'date'|trans }}</strong></th>
|
||||
<th><strong>{{ 'description'|trans }}</strong></th>
|
||||
<th class="text-right"><strong>{{ 'unit_price'|trans }}</strong></th>
|
||||
<th class="text-right"><strong>{{ 'amount'|trans }}</strong></th>
|
||||
<th class="last text-right"><strong>{{ 'total_rate'|trans }}</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -114,7 +118,7 @@ mpdf-->
|
||||
{% else %}
|
||||
{% set rate = entry.hourlyRate %}
|
||||
{% endif %}
|
||||
<tr{# class="{{ cycle(['odd', 'even'], id) }}"#}>
|
||||
<tr>
|
||||
<td nowrap class="first text-nowrap">{{ entry.begin|date_short }}</td>
|
||||
<td contenteditable="true">
|
||||
{% if entry.description is not empty %}
|
||||
@@ -160,6 +164,5 @@ mpdf-->
|
||||
{{ model.template.paymentTerms|md2html }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,13 +1,14 @@
|
||||
{% extends 'invoice/layout.html.twig' %}
|
||||
|
||||
{% block invoice %}
|
||||
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||
{% set isDecimal = true %}
|
||||
{% set currency = model.currency %}
|
||||
{% set project = model.query.project %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<h2 class="page-header">
|
||||
<span contenteditable="true">{{ model.template.title }}</span>
|
||||
<small class="pull-right">{{ 'label.date'|trans }}: {{ model.invoiceDate|date_short }}</small>
|
||||
<small class="pull-right">{{ 'date'|trans }}: {{ model.invoiceDate|date_short }}</small>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,15 +21,15 @@
|
||||
{{ model.customer.address|nl2br }}
|
||||
{% if model.customer.vatId is not empty %}
|
||||
<br>
|
||||
{{ 'label.vat_id'|trans }}: {{ model.customer.vatId }}
|
||||
{{ 'vat_id'|trans }}: {{ model.customer.vatId }}
|
||||
{% endif %}
|
||||
{% if model.customer.number is not empty %}
|
||||
<br>
|
||||
{{ 'label.number'|trans }}: {{ model.customer.number }}
|
||||
{{ 'number'|trans }}: {{ model.customer.number }}
|
||||
{% endif %}
|
||||
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
|
||||
{% if project is not null and project.orderNumber is not empty %}
|
||||
<br>
|
||||
{{ 'label.orderNumber'|trans }}: {{ model.query.project.orderNumber }}
|
||||
{{ 'orderNumber'|trans }}: {{ project.orderNumber }}
|
||||
{% endif %}
|
||||
</address>
|
||||
</div>
|
||||
@@ -40,7 +41,7 @@
|
||||
{{ model.template.address|trim|nl2br }}
|
||||
{% if model.template.vatId is not empty %}
|
||||
<br>
|
||||
{{ 'label.vat_id'|trans }}:
|
||||
{{ 'vat_id'|trans }}:
|
||||
{{ model.template.vatId }}
|
||||
{% endif %}
|
||||
</address>
|
||||
@@ -66,11 +67,11 @@
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.date'|trans }}</th>
|
||||
<th>{{ 'label.description'|trans }}</th>
|
||||
<th class="text-right">{{ 'label.unit_price'|trans }}</th>
|
||||
<th class="text-right">{{ 'label.amount'|trans }}</th>
|
||||
<th class="text-right">{{ 'label.total_rate'|trans }}</th>
|
||||
<th>{{ 'date'|trans }}</th>
|
||||
<th>{{ 'description'|trans }}</th>
|
||||
<th class="text-end">{{ 'unit_price'|trans }}</th>
|
||||
<th class="text-end">{{ 'amount'|trans }}</th>
|
||||
<th class="text-end">{{ 'total_rate'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -91,32 +92,32 @@
|
||||
{% if entry.activity is not null %}{{ entry.activity.name }} / {% endif %}{{ entry.project.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td nowrap class="text-nowrap text-right">{{ rate|money(currency) }}</td>
|
||||
<td nowrap class="text-nowrap text-right">{{ duration }}</td>
|
||||
<td nowrap class="text-nowrap text-right">{{ entry.rate|money(currency) }}</td>
|
||||
<td nowrap class="text-nowrap text-end">{{ rate|money(currency) }}</td>
|
||||
<td nowrap class="text-nowrap text-end">{{ duration }}</td>
|
||||
<td nowrap class="text-nowrap text-end">{{ entry.rate|money(currency) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
{% if model.calculator.tax > 0 or not model.isHideZeroTax() %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-right">
|
||||
<td colspan="4" class="text-end">
|
||||
{{ 'invoice.subtotal'|trans }}
|
||||
</td>
|
||||
<td class="text-right">{{ model.calculator.subtotal|money(currency) }}</td>
|
||||
<td class="text-end">{{ model.calculator.subtotal|money(currency) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="text-right">
|
||||
<td colspan="4" class="text-end">
|
||||
{{ 'invoice.tax'|trans }} ({{ model.calculator.vat }}%)
|
||||
</td>
|
||||
<td class="text-right">{{ model.calculator.tax|money(currency) }}</td>
|
||||
<td class="text-end">{{ model.calculator.tax|money(currency) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-right text-nowrap">
|
||||
<td colspan="4" class="text-end text-nowrap">
|
||||
<strong>{{ 'invoice.total'|trans }}</strong>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<td class="text-end">
|
||||
<strong>{{ model.calculator.total|money(currency) }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -137,11 +138,11 @@
|
||||
|
||||
<footer class="footer">
|
||||
<p>
|
||||
<strong>{{ 'label.address'|trans }}</strong>: {{ model.template.company }} – {{ model.template.address|nl2str(' – ') }}
|
||||
<strong>{{ 'address'|trans }}</strong>: {{ model.template.company }} – {{ model.template.address|nl2str(' – ') }}
|
||||
<br>
|
||||
<strong>{{ 'label.invoice_bank_account'|trans }}</strong>: {{ model.template.paymentDetails|nl2str(' – ') }}
|
||||
<strong>{{ 'invoice_bank_account'|trans }}</strong>: {{ model.template.paymentDetails|nl2str(' – ') }}
|
||||
<br>
|
||||
<strong>{{ 'label.contact'|trans }}</strong>: {{ model.template.contact|nl2str(' – ') }}
|
||||
<strong>{{ 'contact'|trans }}</strong>: {{ model.template.contact|nl2str(' – ') }}
|
||||
</p>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
@@ -1,6 +0,0 @@
|
||||
{% set items = [] %}
|
||||
{% for entry in entries %}
|
||||
{% set items = items|merge([entry]) %}
|
||||
{% endfor %}
|
||||
{% set json = invoice|merge({'items': items}) %}
|
||||
{{ json|json_encode|raw}}
|
||||
@@ -1,13 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
{% set fallback = app.request is not null ? app.request.locale : 'en' %}
|
||||
{% set language = model.template.language|default(fallback) %}
|
||||
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||
{% set currency = model.currency %}
|
||||
{%- set fallback = app.request is not null ? app.request.locale : 'en' -%}
|
||||
{%- set language = model.template.language|default(fallback) -%}
|
||||
{%- set isDecimal = true -%}
|
||||
{%- set currency = model.currency -%}
|
||||
{%- set project = model.query.project -%}
|
||||
<html lang="{{ language }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}{{ model.invoiceNumber }}-{{ model.customer.company|default(model.customer.name)|u.snake }}{% endblock %}</title>
|
||||
<style type="text/css">
|
||||
<style>
|
||||
{{ encore_entry_css_source('invoice-pdf')|raw }}
|
||||
</style>
|
||||
</head>
|
||||
@@ -19,7 +20,7 @@
|
||||
<td class="text-small">
|
||||
{{ model.template.company }} – {{ model.template.address|nl2str(' – ') }}
|
||||
{% if model.template.vatId is not empty %}
|
||||
– {{ 'label.vat_id'|trans }}: {{ model.template.vatId }}
|
||||
– {{ 'vat_id'|trans }}: {{ model.template.vatId }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-small text-right">
|
||||
@@ -33,18 +34,18 @@
|
||||
<table class="footer text-small">
|
||||
<tr>
|
||||
<td style="width: 33%">
|
||||
<strong>{{ 'label.address'|trans }}</strong>
|
||||
<strong>{{ 'address'|trans }}</strong>
|
||||
<br>
|
||||
{{ model.template.company }}<br>
|
||||
{{ model.template.address|nl2br }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<strong>{{ 'label.invoice_bank_account'|trans }}</strong>
|
||||
<strong>{{ 'invoice_bank_account'|trans }}</strong>
|
||||
<br>
|
||||
{{ model.template.paymentDetails|nl2br }}
|
||||
</td>
|
||||
<td class="text-right" style="width: 33%">
|
||||
<strong>{{ 'label.contact'|trans }}</strong>
|
||||
<strong>{{ 'contact'|trans }}</strong>
|
||||
<br>
|
||||
{{ model.template.contact|nl2br }}
|
||||
</td>
|
||||
@@ -53,7 +54,6 @@
|
||||
</htmlpagefooter>
|
||||
<sethtmlpagefooter page="ALL" value="on" name="footer" show-this-page="1" />
|
||||
mpdf-->
|
||||
<div class="wrapper">
|
||||
<table class="addresses">
|
||||
<tr>
|
||||
<td>
|
||||
@@ -65,7 +65,7 @@ mpdf-->
|
||||
{% set classRight = 'text-right text-nowrap padding-left' %}
|
||||
<table style="width: 240px">
|
||||
<tr>
|
||||
<td class="{{ classLeft }}">{{ 'label.date'|trans }}</td>
|
||||
<td class="{{ classLeft }}">{{ 'date'|trans }}</td>
|
||||
<td class="{{ classRight }}">{{ model.invoiceDate|date_short }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -82,19 +82,19 @@ mpdf-->
|
||||
</tr>
|
||||
{% if model.customer.number is not empty %}
|
||||
<tr>
|
||||
<td class="{{ classLeft }}">{{ 'label.number'|trans }}</td>
|
||||
<td class="{{ classLeft }}">{{ 'number'|trans }}</td>
|
||||
<td class="{{ classRight }}">{{ model.customer.number }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
|
||||
{% if project is not null and project.orderNumber is not empty %}
|
||||
<tr>
|
||||
<td class="{{ classLeft }}">{{ 'label.orderNumber'|trans }}</td>
|
||||
<td class="{{ classRight }}">{{ model.query.project.orderNumber }}</td>
|
||||
<td class="{{ classLeft }}">{{ 'orderNumber'|trans }}</td>
|
||||
<td class="{{ classRight }}">{{ project.orderNumber }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if model.customer.vatId is not empty %}
|
||||
<tr>
|
||||
<td class="{{ classLeft }}">{{ 'label.vat_id'|trans }}</td>
|
||||
<td class="{{ classLeft }}">{{ 'vat_id'|trans }}</td>
|
||||
<td class="{{ classRight }}">{{ model.customer.vatId }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -105,13 +105,17 @@ mpdf-->
|
||||
|
||||
<h2 style="margin-top: 60px">{{ model.template.title }}</h2>
|
||||
|
||||
{% if model.customer.invoiceText is not empty%}
|
||||
<p>{{ model.customer.invoiceText|md2html }}</p>
|
||||
{% endif %}
|
||||
|
||||
<table class="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="first">{{ 'label.description'|trans }}</th>
|
||||
<th class="text-right">{{ 'label.unit_price'|trans }}</th>
|
||||
<th class="text-right">{{ 'label.amount'|trans }}</th>
|
||||
<th class="last text-right">{{ 'label.total_rate'|trans }}</th>
|
||||
<th class="first">{{ 'description'|trans }}</th>
|
||||
<th class="text-right">{{ 'unit_price'|trans }}</th>
|
||||
<th class="text-right">{{ 'amount'|trans }}</th>
|
||||
<th class="last text-right">{{ 'total_rate'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -170,6 +174,5 @@ mpdf-->
|
||||
{{ model.template.paymentTerms|md2html }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +0,0 @@
|
||||
{% for key, value in invoice %}
|
||||
{{ key }}
|
||||
{% if value is not empty %}
|
||||
{{ value|multiline_indent(' ') }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for entry in entries %}
|
||||
---
|
||||
{% for key, value in entry %}
|
||||
{{ key }}
|
||||
{% if value is not empty %}
|
||||
{{ value|multiline_indent(' ') }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
@@ -2,7 +2,9 @@
|
||||
{% extends 'invoice/layout.html.twig' %}
|
||||
|
||||
{% block invoice %}
|
||||
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||
{% set isDecimal = true %}
|
||||
{% set project = model.query.project %}
|
||||
{% set activity = model.query.activity %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<h2 class="page-header">
|
||||
@@ -13,7 +15,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<table class="table no-border table-condensed">
|
||||
<table class="table no-border table-sm">
|
||||
<tr>
|
||||
<th>{{ 'invoice.from'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
@@ -25,7 +27,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'label.date'|trans }}</th>
|
||||
<th>{{ 'date'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
{% if model.query.begin|date('m') != model.query.end|date('m') or model.query.begin|date('Y') != model.query.end|date('Y') %}
|
||||
{{ model.query.begin|date_short }} - {{ model.query.end|date_short }}
|
||||
@@ -35,28 +37,28 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th>{{ 'customer'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
{% if model.customer.number is not empty %}[{{ model.customer.number }}]{% endif %}
|
||||
{{ model.customer.name }}{% if model.customer.contact is not empty %} / {{ model.customer.contact }}{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if model.query.project is not empty %}
|
||||
{% if project is not null %}
|
||||
<tr>
|
||||
<th>{{ 'label.project'|trans }}</th>
|
||||
<th>{{ 'project'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
{{ model.query.project.name }}
|
||||
{% if model.query.project.orderNumber is not empty %}
|
||||
({{ 'label.orderNumber'|trans }}: {{ model.query.project.orderNumber }})
|
||||
{{ project.name }}
|
||||
{% if project.orderNumber is not empty %}
|
||||
({{ 'orderNumber'|trans }}: {{ project.orderNumber }})
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if model.query.activity is not empty %}
|
||||
{% if activity is not null %}
|
||||
<tr>
|
||||
<th>{{ 'label.activity'|trans }}</th>
|
||||
<th>{{ 'activity'|trans }}</th>
|
||||
<td contenteditable="true">
|
||||
{{ model.query.activity.name }}
|
||||
{{ activity.name }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -69,12 +71,12 @@
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.date'|trans }}</th>
|
||||
<th>{{ 'date'|trans }}</th>
|
||||
{% if model.query.user is empty %}
|
||||
<th>{{ 'label.user'|trans }}</th>
|
||||
<th>{{ 'user'|trans }}</th>
|
||||
{% endif %}
|
||||
<th>{{ 'label.activity'|trans }}</th>
|
||||
<th class="text-right">{{ 'label.hours'|trans }}</th>
|
||||
<th>{{ 'activity'|trans }}</th>
|
||||
<th class="text-right">{{ 'hours'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -112,7 +114,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{% if model.template.paymentTerms is not empty %}
|
||||
<p class="lead">{{ 'label.payment_terms'|trans }}</p>
|
||||
<p class="lead">{{ 'payment_terms'|trans }}</p>
|
||||
|
||||
<p class="text-muted well well-sm no-shadow" contenteditable="true" style="margin-bottom: 100px">
|
||||
{{ model.template.paymentTerms|trim|nl2br }}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kimai version="{{ constant('App\\Constants::VERSION') }}">
|
||||
{% for key, value in invoice %}
|
||||
<{{ key }}{% if value is empty %}/>{% else %}>{{ value|escape }}</{{ key }}>{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
<items>
|
||||
{%- for entry in entries %}
|
||||
|
||||
<item>
|
||||
{% for key, value in entry %}
|
||||
<{{ key }}{% if value is empty %}/>{% else %}>{{ value|escape }}</{{ key }}>{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
</item>
|
||||
{%- endfor %}
|
||||
|
||||
</items>
|
||||
</kimai>
|
||||
@@ -1,9 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_invoice_template.title'|trans }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% 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': template.name|default('create'|trans),
|
||||
'form': form,
|
||||
@@ -48,7 +46,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{{ form_row(form.renderer) }}
|
||||
{{ form_row(form.renderer, {'help_html': true, 'help': '<a href="https://github.com/kimai/invoice-templates" target="_blank">' ~ ('download_invoice_renderer'|trans({}, 'invoice-renderer')) ~ '</a>'}) }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ form_row(form.language) }}
|
||||
@@ -62,12 +60,7 @@
|
||||
{{ form_row(form.numberGenerator) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{{ form_row(form.decimalDuration) }}
|
||||
</div>
|
||||
</div>
|
||||
{{ form_widget(form) }}
|
||||
{{ form_rest(form) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,56 +1,23 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% extends 'datatable.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "invoice/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'name': {'class': 'alwaysVisible text-nowrap', 'orderBy': false},
|
||||
'title': {'class': 'hidden-xs text-nowrap', 'orderBy': false},
|
||||
'company': {'class': 'hidden-xs hidden-sm hidden', 'orderBy': false},
|
||||
'vat_id': {'class': 'hidden-xs hidden-sm text-nowrap', 'orderBy': false},
|
||||
'tax_rate': {'class': 'hidden-xs hidden-sm hidden-md text-nowrap', 'orderBy': false},
|
||||
'due_days': {'class': 'hidden-xs hidden-sm hidden-md text-nowrap', 'orderBy': false},
|
||||
'address': {'class': 'hidden', 'orderBy': false},
|
||||
'contact': {'class': 'hidden', 'orderBy': false},
|
||||
'calculator': {'title': 'label.invoice_calculator'|trans({}, 'invoice-calculator'), 'class': 'hidden', 'orderBy': false},
|
||||
'renderer': {'title': 'label.invoice_renderer'|trans({}, 'invoice-renderer'), 'class': 'hidden', 'orderBy': false},
|
||||
'language': {'class': 'text-nowrap', 'orderBy': false},
|
||||
'actions': {'class': 'actions alwaysVisible', 'orderBy': false},
|
||||
} %}
|
||||
|
||||
{% set tableName = 'invoice_template' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_invoice_template.title'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.invoice_templates('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.nothing_found('kimai.invoiceTemplateUpdate') }}
|
||||
{% endif %}
|
||||
|
||||
{{ tables.datatable_header(tableName, columns, null, {'reload': 'kimai.invoiceTemplateUpdate'}) }}
|
||||
{% for entry in entries %}
|
||||
<tr class="modal-ajax-form open-edit" data-href="{{ path('admin_invoice_template_edit', {'id' : entry.id}) }}">
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ entry.name }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'title') }}">{{ entry.title }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'company') }}">{{ entry.company }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'vat_id') }}">{{ entry.vatId }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'tax_rate') }}">{{ entry.vat }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'due_days') }}">{{ entry.dueDays }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'address') }}">{{ entry.address|nl2br }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'contact') }}">{{ entry.contact|nl2br }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'calculator') }}">{{ entry.calculator|trans({}, 'invoice-calculator') }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'renderer') }}">{{ entry.renderer|trans({}, 'invoice-renderer') }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'language') }}">{% if entry.language is not empty %}{{ entry.language|language }}{% endif %}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
|
||||
{{- actions.invoice_template(entry, 'index') -}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{{ tables.data_table_footer(entries, 'admin_invoice_template') }}
|
||||
{% block datatable_row %}
|
||||
<tr class="modal-ajax-form open-edit" data-href="{{ path('admin_invoice_template_edit', {'id' : entry.id}) }}">
|
||||
<td class="{{ tables.class(dataTable, 'name') }}">{{ entry.name }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'title') }}">{{ entry.title }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'company') }}">{{ entry.company }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'vat_id') }}">{{ entry.vatId }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'tax_rate') }}">{{ entry.vat }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'due_days') }}">{{ entry.dueDays }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'address') }}">{{ entry.address|nl2br }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'contact') }}">{{ entry.contact|nl2br }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'calculator') }}">{{ entry.calculator|trans({}, 'invoice-calculator') }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'renderer') }}">{{ entry.renderer|trans({}, 'invoice-renderer') }}</td>
|
||||
<td class="{{ tables.class(dataTable, 'language') }}">{% if entry.language is not empty %}{{ entry.language|locale_name }}{% endif %}</td>
|
||||
<td class="{{ tables.class(dataTable, 'actions') }}">
|
||||
{{- actions.invoice_template(entry, 'index') -}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endblock %}
|
||||
|
||||
6
templates/macros/breadcrumb.html.twig
Normal file
6
templates/macros/breadcrumb.html.twig
Normal file
@@ -0,0 +1,6 @@
|
||||
{% macro breadcrumb(breadcrumbs) %}
|
||||
{% from '@theme/components/breadcrumb.html.twig' import breadcrumb %}
|
||||
<div class="btn-list">
|
||||
{{ breadcrumb(breadcrumbs, 'arrows') }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -4,47 +4,51 @@
|
||||
{# ------------------------------------------------------------------------------ #}
|
||||
|
||||
{#
|
||||
options = {'label': 'label', 'title': string|null, 'footer': string|null, 'legend': false}
|
||||
options = {'label': 'label', 'title': string|null, 'footer': string|null, 'legend': {'display': false}}
|
||||
#}
|
||||
{% macro doughnut_javascript(options) %}
|
||||
<script type="text/javascript">
|
||||
function doughnutChartOptions() {
|
||||
return {
|
||||
legend: {% if options.legend is defined %}{{ options.legend }}{% else %}false{% endif %},
|
||||
maintainAspectRatio: true,
|
||||
maintainAspectRatio: false,
|
||||
responsive: true,
|
||||
cutoutPercentage: 40,
|
||||
animation: {
|
||||
animateRotate: true
|
||||
},
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: function(tooltipItem, data) {
|
||||
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
return ' ' + tooltipData['{{ options.label|default('label') }}'];
|
||||
},
|
||||
afterTitle: function(tooltipItems, data) {
|
||||
return ' ';
|
||||
},
|
||||
{% if options.title is defined and options.title is not empty %}
|
||||
title: function(tooltipItems, data) {
|
||||
var tooltipItem = tooltipItems[0];
|
||||
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
return ' ' + tooltipData['{{ options.title }}'];
|
||||
},
|
||||
{% endif %}
|
||||
{% if options.footer is defined and options.footer is not empty %}
|
||||
beforeFooter: function(tooltipItems, data) {
|
||||
return ' ';
|
||||
},
|
||||
footer: function(tooltipItems, data) {
|
||||
var tooltipItem = tooltipItems[0];
|
||||
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
return tooltipData['{{ options.footer }}'];
|
||||
},
|
||||
{% endif %}
|
||||
plugins: {
|
||||
{% if options.legend is defined and options.legend is iterable %}
|
||||
legend: {{ options.legend|json_encode|raw }},
|
||||
{% endif %}
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(tooltipItem) {
|
||||
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
|
||||
return ' ' + tooltipData['{{ options.label|default('label') }}'];
|
||||
},
|
||||
afterTitle: function(tooltipItems) {
|
||||
return ' ';
|
||||
},
|
||||
{% if options.title is defined and options.title is not empty %}
|
||||
title: function(tooltipItems) {
|
||||
const tooltipItem = tooltipItems[0];
|
||||
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
|
||||
return ' ' + tooltipData['{{ options.title }}'];
|
||||
},
|
||||
{% endif %}
|
||||
{% if options.footer is defined and options.footer is not empty %}
|
||||
beforeFooter: function(tooltipItems) {
|
||||
return ' ';
|
||||
},
|
||||
footer: function(tooltipItems) {
|
||||
const tooltipItem = tooltipItems[0];
|
||||
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
|
||||
return tooltipData['{{ options.footer }}'];
|
||||
},
|
||||
{% endif %}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
@@ -59,13 +63,13 @@
|
||||
{% macro doughnut_chart(id, labels, dataset, options) %}
|
||||
{% set height = options.height|default(config('theme.chart.height') ~ 'px') %}
|
||||
{% set bgColor = config('theme.chart.background_color') %}
|
||||
<div class="chart" style="position: relative;">
|
||||
<canvas id="{{ id }}" style="height: {{ height }};"></canvas>
|
||||
<div class="{{ config('chart-class') }}" style="position: relative;">
|
||||
<canvas id="{{ id }}" style="height: {{ height }}; width: 100%;"></canvas>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('{{ options.renderEvent|default('kimai.initialized') }}', function() {
|
||||
new Chart(
|
||||
$("#{{ id }}").get(0).getContext("2d"), {
|
||||
document.getElementById("{{ id }}").getContext("2d"), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
{% if labels is not empty %}
|
||||
@@ -100,24 +104,23 @@
|
||||
{# ------------------------------------------------------------------- #}
|
||||
|
||||
{#
|
||||
options = {'label': 'label', 'title': string|null, 'footer': string|null, 'legend': false, 'onclick': {'url': string, 'replacer': {'name': 'value'}}}
|
||||
options = {'label': 'label', 'title': string|null, 'footer': string|null, 'legend': {'display': false}, 'onclick': {'url': string, 'replacer': {'name': 'value'}}}
|
||||
#}
|
||||
{% macro bar_javascript(options) %}
|
||||
<script type="text/javascript">
|
||||
function barChartOptions() {
|
||||
return {
|
||||
legend: {% if options.legend is defined %}{{ options.legend }}{% else %}false{% endif %},
|
||||
maintainAspectRatio: true,
|
||||
responsive: true,
|
||||
barPercentage: 0.5,
|
||||
categoryPercentage: 0.9,
|
||||
categoryPercentage: 1.0,
|
||||
scales: {
|
||||
xAxes: [{
|
||||
x: {
|
||||
gridLines: {
|
||||
display: false
|
||||
}
|
||||
}],
|
||||
yAxes: [{
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
@@ -126,44 +129,52 @@
|
||||
color: '{{ config('theme.chart.grid_color') }}',
|
||||
lineWidth: 1
|
||||
}
|
||||
}]
|
||||
},
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: function(tooltipItem, data) {
|
||||
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
return ' ' + tooltipData['{{ options.label|default('label') }}'];
|
||||
},
|
||||
afterTitle: function(tooltipItems, data) {
|
||||
return ' ';
|
||||
},
|
||||
{% if options.title is defined and options.title is not empty %}
|
||||
title: function(tooltipItems, data) {
|
||||
var tooltipItem = tooltipItems[0];
|
||||
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
return ' ' + tooltipData['{{ options.title }}'];
|
||||
},
|
||||
{% endif %}
|
||||
{% if options.footer is defined and options.footer is not empty %}
|
||||
beforeFooter: function(tooltipItems, data) {
|
||||
return ' ';
|
||||
},
|
||||
footer: function(tooltipItems, data) {
|
||||
var tooltipItem = tooltipItems[0];
|
||||
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
|
||||
return tooltipData['{{ options.footer }}'];
|
||||
},
|
||||
{% endif %}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
{% if options.legend is defined and options.legend is iterable %}
|
||||
legend: {{ options.legend|json_encode|raw }},
|
||||
{% endif %}
|
||||
tooltip: {
|
||||
titleSpacing: 0,
|
||||
titleMarginBottom: 0,
|
||||
footerMarginTop: 0,
|
||||
callbacks: {
|
||||
label: function(tooltipItem) {
|
||||
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
|
||||
return ' ' + tooltipData['{{ options.label|default('label') }}'];
|
||||
},
|
||||
afterTitle: function(tooltipItems) {
|
||||
return ' ';
|
||||
},
|
||||
{% if options.title is defined and options.title is not empty %}
|
||||
title: function(tooltipItems) {
|
||||
const tooltipItem = tooltipItems[0];
|
||||
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
|
||||
return ' ' + tooltipData['{{ options.title }}'];
|
||||
},
|
||||
{% endif %}
|
||||
{% if options.footer is defined and options.footer is not empty %}
|
||||
beforeFooter: function(tooltipItems) {
|
||||
return ' ';
|
||||
},
|
||||
footer: function(tooltipItems) {
|
||||
const tooltipItem = tooltipItems[0];
|
||||
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
|
||||
return tooltipData['{{ options.footer }}'];
|
||||
},
|
||||
{% endif %}
|
||||
}
|
||||
},
|
||||
},
|
||||
{% if options.onclick is defined and options.onclick.url is defined %}
|
||||
onClick: function(event, elements) {
|
||||
onClick: function(event, elements, chart) {
|
||||
if (elements.length !== 1) {
|
||||
return;
|
||||
}
|
||||
var element = elements[0];
|
||||
var data = this.data.datasets[element._datasetIndex].realData[element._index];
|
||||
var url = '{{ options.onclick.url|raw }}';
|
||||
const element = elements[0];
|
||||
const data = this.data.datasets[element.datasetIndex].realData[element.index];
|
||||
let url = '{{ options.onclick.url|raw }}';
|
||||
{% if options.onclick.replacer is defined %}
|
||||
{% for replacer, field in options.onclick.replacer %}
|
||||
url = url.replace(/{{ replacer }}/, data['{{ field }}']);
|
||||
@@ -186,8 +197,8 @@
|
||||
{% macro bar_chart(id, labels, datasets, options) %}
|
||||
{% set height = options.height|default(config('theme.chart.height') ~ 'px') %}
|
||||
{% set bgColor = config('theme.chart.background_color') %}
|
||||
<div class="chart" style="position: relative;">
|
||||
<canvas id="{{ id }}" style="height: {{ height }};"></canvas>
|
||||
<div class="{{ config('chart-class') }}" style="position: relative;">
|
||||
<canvas id="{{ id }}" style="height: {{ height }}; width: 100%;"></canvas>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('{{ options.renderEvent|default('kimai.initialized') }}', function() {
|
||||
|
||||
@@ -1,37 +1,161 @@
|
||||
|
||||
{% macro configuration(dataTable) %}
|
||||
{{ _self.data_table_column_modal(dataTable.getTableName(), dataTable.getColumns()) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro header(dataTable) %}
|
||||
{{ _self.datatable_header(dataTable.getTableName(), dataTable.getColumns(), dataTable.getQuery(), dataTable.getOptions()) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro footer(dataTable) %}
|
||||
{{ _self.data_table_footer(dataTable.getPagination(), dataTable.getPaginationRoute(), dataTable.getBatchForm()) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro class(dataTable, column) %}
|
||||
{{- datatable_column_class(dataTable.getTableName(), column) -}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro actions(dataTable) %}
|
||||
{% set btnClass = '' %}
|
||||
{% set bgClass = '' %}
|
||||
{% if not tabler_bundle.isNavbarOverlapping() and not tabler_bundle.isDarkMode() %}
|
||||
{% set btnClass = 'btn-white' %}
|
||||
{% set bgClass = 'bg-white' %}
|
||||
{% endif %}
|
||||
<div class="btn-list">
|
||||
{% if dataTable.hasConfiguration() %}
|
||||
<a class="btn btn-icon {{ btnClass }}" href="#" data-bs-toggle="modal" data-bs-target="#modal_{{ dataTable.getTableName() }}">
|
||||
{{ icon('columns', true) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% set form = dataTable.getSearchForm() %}
|
||||
{% if form is not null %}
|
||||
{% form_theme form 'form/search.html.twig' %}
|
||||
{% form_theme form.order 'form/vertical.html.twig' %}
|
||||
{{ form_start(form, {'attr': {'class': 'searchform'}}) }}
|
||||
{% set searchTerm = form_widget(form.searchTerm) %}
|
||||
{% set orderHasError = form.orderBy.vars.errors|length > 0 or form.order.vars.errors|length > 0 %}
|
||||
{% set orderBy = form_widget(form.orderBy) %}
|
||||
{% set order = form_widget(form.order) %}
|
||||
{% set filterCount = dataTable.getQuery().countFilter() %}
|
||||
<div class="input-group inline-search position-static">
|
||||
<button type="button" class="btn {{ btnClass }} dropdown-toggle" data-bs-toggle="dropdown" data-bs-auto-close="false" aria-haspopup="true" aria-expanded="false" id="search-dropdown-btn">
|
||||
{{ icon('filter', true) }}
|
||||
{% if filterCount > 0 %}<span class="badge badge-pill bg-blue ms-1 d-none d-md-inline">{{ filterCount }}</span>{% endif %}
|
||||
</button>
|
||||
<div class="dropdown-menu p-3 search-dropdown" data-popper-placement="bottom-start" aria-labelledby="search-dropdown-btn">
|
||||
{{ form_rest(form) }}
|
||||
<div class="mb-2 row {% if orderHasError %} is-invalid{% endif %} ">
|
||||
{{ form_label(form.orderBy) }}
|
||||
<div class="col-sm-4 col-xs-6">
|
||||
{{ orderBy|raw }}
|
||||
{{ form_errors(form.orderBy) }}
|
||||
</div>
|
||||
<div class="col-sm-4 col-xs-6">
|
||||
{{ order|raw }}
|
||||
{{ form_errors(form.order) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 text-end">
|
||||
<button type="submit" class="btn btn-primary">{{ 'search'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ searchTerm|raw }}
|
||||
<span class="input-group-text {{ bgClass }}">
|
||||
{% set bookmarkToken = csrf_token('search') %}
|
||||
{% set searchBtnClass = 'btn btn-icon btn-sm btn-ghost-secondary' %}
|
||||
<input type="hidden" name="_token" value="{{ bookmarkToken }}">
|
||||
<button type="submit" class="{{ searchBtnClass }}" data-toggle="tooltip" title="{{ 'search'|trans }}">
|
||||
{{ icon('search') }}
|
||||
</button>
|
||||
|
||||
{% if form.vars.data.bookmark %}
|
||||
<a class="{{ searchBtnClass }} text-yellow" href="?_token={{ bookmarkToken }}&removeDefaultQuery={{ form.vars.data.bookmark.name }}" data-toggle="tooltip" title="{{ 'remove_default'|trans }}" id="removeDefaultQuery">
|
||||
{{ icon('bookmarked') }}
|
||||
</a>
|
||||
{% elseif filterCount > 0 %}
|
||||
<button class="{{ searchBtnClass }}" type="submit" id="setDefaultQuery" name="setDefaultQuery" data-toggle="tooltip" title="{{ 'set_as_default'|trans }}">
|
||||
{{ icon('bookmark') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if filterCount > 0 and not form.vars.data.isBookmarkSearch() %}
|
||||
<button class="{{ searchBtnClass }} text-orange" type="submit" id="resetSearchFilter" name="resetSearchFilter" data-toggle="tooltip" title="{{ 'remove_filter'|trans }}">
|
||||
{{ icon('cancel') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<input type="hidden" name="performSearch" value="performSearch" />
|
||||
{{ form_end(form) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro data_table_column_modal(name, columns) %}
|
||||
<div class="modal fade" id="modal_{{ name }}" data-column-visibility="{{ name }}" tabindex="-1" role="dialog" aria-labelledby="data_table_modal_label">
|
||||
<div class="modal-dialog" role="document">
|
||||
{% set visibility = initialize_datatable(app.user, app.session, name, columns) %}
|
||||
<div class="modal modal-blur fade" data-bs-backdrop="static" id="modal_{{ name }}" data-column-visibility="{{ name }}" tabindex="-1" role="dialog" aria-labelledby="data_table_modal_label">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="{{ 'action.close'|trans }}"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="data_table_modal_label">{{ 'modal.columns.title'|trans }}</h4>
|
||||
<h5 class="modal-title" id="data_table_modal_label">{{ 'modal.columns.title'|trans }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ 'action.close'|trans }}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="{{ name }}_visibility">
|
||||
{%- for title, headerOptions in columns -%}
|
||||
{% if not headerOptions is iterable %}
|
||||
{% set headerOptions = {'class': headerOptions} %}
|
||||
{% endif %}
|
||||
{% if headerOptions.title is defined %}
|
||||
{% set headerTitle = headerOptions.title %}
|
||||
{% else %}
|
||||
{% set headerTitle = ('label.' ~ title)|trans %}
|
||||
{% endif %}
|
||||
{% if 'alwaysVisible' not in headerOptions.class %}
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="column_{{ title }}" name="{{ title }}"{% if is_visible_column(name, title, columns) %} checked="checked"{% endif %}>
|
||||
<label class="control-label" for="column_{{ title }}">{{ headerTitle }}</label>
|
||||
<form name="{{ name }}_visibility" method="post" action="{{ path('bookmark_save_datatable') }}" class="mb-3">
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label">{{ 'modal.columns.label'|trans }}</label>
|
||||
<div class="form-selectgroup form-selectgroup-pills">
|
||||
{%- for title, headerOptions in columns -%}
|
||||
{% set translationDomain = headerOptions.translation_domain ?? 'messages' %}
|
||||
{% if not headerOptions is iterable %}
|
||||
{% set headerOptions = {'class': headerOptions} %}
|
||||
{% endif %}
|
||||
{% if headerOptions.title is defined and headerOptions.title is not empty %}
|
||||
{% set headerTitle = headerOptions.title|trans({}, translationDomain) %}
|
||||
{% else %}
|
||||
{% set headerTitle = title|trans({}, translationDomain) %}
|
||||
{% endif %}
|
||||
{% if 'alwaysVisible' not in headerOptions.class %}
|
||||
<label class="form-selectgroup-item">
|
||||
<input class="form-selectgroup-input" type="checkbox" id="column_{{ title }}" name="{{ title }}">
|
||||
<span class="form-selectgroup-label">{{ headerTitle }}</span>
|
||||
</label>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<label class="form-label">{{ 'modal.columns.profile'|trans }}</label>
|
||||
<div class="form-selectgroup">
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="datatable_profile" value="{{ constant('\\App\\Utils\\ProfileManager::PROFILE_DESKTOP') }}" class="form-selectgroup-input" data-href="{{ path('bookmark_profile') }}"{% if app.session.get(constant('\\App\\Utils\\ProfileManager::SESSION_PROFILE'), '') == '' %} checked="checked"{% endif %}>
|
||||
<span class="form-selectgroup-label">
|
||||
{{ 'desktop'|trans }}
|
||||
</span>
|
||||
</label>
|
||||
<label class="form-selectgroup-item">
|
||||
<input type="radio" name="datatable_profile" value="{{ constant('\\App\\Utils\\ProfileManager::PROFILE_MOBILE') }}" class="form-selectgroup-input" data-href="{{ path('bookmark_profile') }}"{% if app.session.get(constant('\\App\\Utils\\ProfileManager::SESSION_PROFILE'), '') == 'mobile' %} checked="checked"{% endif %}>
|
||||
<span class="form-selectgroup-label">
|
||||
{{ 'mobile'|trans }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="datatable_name" value="{{ name }}" />
|
||||
<input type="hidden" name="datatable_token" value="{{ csrf_token(constant('\\App\\Controller\\BookmarkController::DATATABLE_TOKEN')) }}" />
|
||||
</form>
|
||||
<p>{{ 'modal.columns.description'|trans }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary pull-left" data-type="save">{{ 'action.save'|trans }}</button>
|
||||
<button type="button" class="btn btn-warning pull-left" data-type="reset">{{ 'action.delete'|trans }}</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||
<button type="submit" formaction="{{ path('bookmark_delete') }}" class="btn btn-outline-danger me-auto" data-type="reset">{{ 'action.reset'|trans }}</button>
|
||||
<button type="submit" class="btn btn-primary" data-type="save">{{ 'action.save'|trans }}</button>
|
||||
<button type="button" class="btn" data-bs-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,6 +163,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro datatable_header(tableName, columns, query, options) %}
|
||||
{% set visibility = initialize_datatable(app.user, app.session, tableName, columns) %}
|
||||
{% if query is not null %}
|
||||
{% set orderBy = options.orderBy|default(query.orderBy) %}
|
||||
{% set order = query.order|lower %}
|
||||
@@ -46,20 +171,22 @@
|
||||
{% set orderBy = false %}
|
||||
{% set order = false %}
|
||||
{% endif %}
|
||||
{% set striped = options.striped ?? false %}
|
||||
{% set bordered = options.bordered ?? false %}
|
||||
{% set reloadEvent = options.reload|default('') %}
|
||||
{% set translationDomain = options.translationDomain|default('messages') %}
|
||||
{% set translationPrefix = options.translationPrefix ?? 'label.' %}
|
||||
{% set boxClass = options.boxClass ?? 'box box-' ~ admin_lte_context.widget.type ~ ' data_table' %}
|
||||
{% set boxClass = options.boxClass ?? 'card data_table' %}
|
||||
{% set columnModal = options.columnConfig ?? true %}
|
||||
{% set responsive = options.responsive ?? false %}
|
||||
{% set sticky = options.sticky ?? true %}
|
||||
{% set contextMenuId = options.contextMenuId is defined ? options.contextMenuId : 'datatable_' ~ tableName ~ '_contextmenu' %}
|
||||
|
||||
<div class="{{ boxClass }} datatable_{{ tableName }}">
|
||||
<div class="box-body no-padding">
|
||||
<div class="dataTables_wrapper form-inline dt-bootstrap">
|
||||
<table class="table {% if striped %}table-striped {% endif %}{% if bordered %}table-bordered {% endif %}table-hover dataTable" role="grid" data-reload-event="{{ reloadEvent }}">
|
||||
<thead>
|
||||
<div class="card-body p-0{% if responsive %} table-responsive{% endif %}">
|
||||
<div class="dataTables_wrapper form-inline">
|
||||
<div id="{{ contextMenuId }}" class="dropdown-menu d-none"></div>
|
||||
<table class="table table-hover dataTable" role="grid" data-reload-event="kimai.reset_column_visibility {{ reloadEvent }}" data-context-menu="{{ contextMenuId }}">
|
||||
<thead{% if sticky %} class="sticky-top"{% endif %}>
|
||||
<tr>
|
||||
{%- for title, headerOptions in columns -%}
|
||||
{% set isActions = title is not empty and title == 'actions' %}
|
||||
{% if not headerOptions is iterable %}
|
||||
{% set headerOptions = {'class': headerOptions} %}
|
||||
{% endif %}
|
||||
@@ -84,19 +211,31 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% set headerTitle = '' %}
|
||||
{% set translationDomain = headerOptions.translation_domain ?? 'messages' %}
|
||||
{% if headerOptions.title is defined %}
|
||||
{% set headerTitle = headerOptions.title %}
|
||||
{% elseif title is not empty and title != 'actions' %}
|
||||
{% set headerTitle = (translationPrefix ~ title)|trans({}, translationDomain) %}
|
||||
{% set headerTitle = headerOptions.title|trans({}, translationDomain) %}
|
||||
{% elseif title is not empty and not isActions %}
|
||||
{% set headerTitle = title|trans({}, translationDomain) %}
|
||||
{% endif %}
|
||||
<th data-field="{{ title }}" {% if not headerOptions.orderBy is same as(false) %}data-order="{{ headerOptions.orderBy }}" role="button" {% endif %}{% if ariaSort is defined %}aria-sort="{{ ariaSort }}" {% endif %}class="{{ headerClass }}">
|
||||
{% if headerOptions.html_before is defined %}
|
||||
{{ headerOptions.html_before|raw }}
|
||||
{% if isActions %}
|
||||
{% set headerClass = headerClass ~ ' text-center' %}
|
||||
{% endif %}
|
||||
<th data-field="{{ title }}" {% if not headerOptions.orderBy is same as(false) %}data-order="{{ headerOptions.orderBy }}" {% endif %}class="{{ headerClass }}"{% if ariaSort is defined %} aria-sort="{{ ariaSort }}"{% endif %}>
|
||||
{% if title is not empty and title == 'actions' and columnModal is not same as (false) %}
|
||||
|
||||
<a class="link-secondary" href="#" data-bs-toggle="modal" data-bs-target="#modal_{{ tableName }}">
|
||||
{{ icon('columns', true) }}
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
{% if headerOptions.batchUpdate is defined %}
|
||||
<input type="checkbox" id="multi_update_all" class="multiupdater form-check-input m-0 align-middle">
|
||||
{% endif %}
|
||||
{{ headerTitle }}
|
||||
{% if headerOptions.html_after is defined %}
|
||||
{{ headerOptions.html_after|raw }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</th>
|
||||
{%- endfor -%}
|
||||
</tr>
|
||||
@@ -105,52 +244,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro data_table_column_class(name, columns, column) %}
|
||||
{% apply spaceless %}
|
||||
{% set class = '' %}
|
||||
{% set always = false %}
|
||||
|
||||
{% if columns[column] is defined %}
|
||||
{% set classes = columns[column] %}
|
||||
{# change the next if, once data_table_header_options() will be deleted #}
|
||||
{% if classes is iterable %}
|
||||
{% set classes = classes.class %}
|
||||
{% if columns[column].columnClass is defined %}
|
||||
{% set classes = classes ~ ' ' ~ columns[column].columnClass %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if 'alwaysVisible' in classes %}
|
||||
{# as this column should always be visible, we remove every class that includes hidden #}
|
||||
{% for tmp in classes|split(' ') %}
|
||||
{% if 'hidden' not in tmp %}
|
||||
{% set class = class ~ ' ' ~ tmp %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if not is_visible_column(name, column, columns) %}
|
||||
{% set classes = classes ~ ' hidden' %}
|
||||
{% elseif not is_datatable_configured(name) %}
|
||||
{# needs to be a foreach, as classes like "hidden-xs" would match in: if 'hidden' in classes #}
|
||||
{% for tmp in classes|split(' ') %}
|
||||
{% if 'hidden' == tmp %}
|
||||
{% set classes = classes|replace({(tmp): ''}) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{# needs to be a foreach, as classes like "hidden-xs" would match in: if 'hidden' in classes #}
|
||||
{% for tmp in classes|split(' ') %}
|
||||
{% if 'hidden' in tmp %}
|
||||
{% set classes = classes|replace({(tmp): ''}) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% set class = classes %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if not class is empty %}
|
||||
{{ class }}
|
||||
{% endif %}
|
||||
{% endapply %}
|
||||
{{- datatable_column_class(name, column) -}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro data_table_footer(entries, route, multi_update_form) %}
|
||||
@@ -158,71 +252,37 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if multi_update_form is not null %}
|
||||
<div id="multi_update_form" style="display: none">
|
||||
{{ form_start(multi_update_form, {'attr': {'data-question': 'update_multiple'|trans}}) }}
|
||||
<label class="col-sm-1 col-xs-2 control-label" for="multi_update_table_action">
|
||||
<span class="pull-left"><i class="fas fa-level-up-alt fa-2x fa-rotate-90"></i></span>
|
||||
</label>
|
||||
<div class="col-sm-11 col-xs-10">
|
||||
{{ form_widget(multi_update_form.action) }}
|
||||
{% if (route is not empty and entries is not null) or multi_update_form is not null%}
|
||||
<div class="card-footer d-flex align-items-center">
|
||||
{% if multi_update_form is not null %}
|
||||
{{ form_start(multi_update_form, {'attr': {'id': 'multi_update_form', 'style': 'display:none', 'data-question': 'update_multiple'|trans}}) }}
|
||||
{% for formChild in multi_update_form.children %}
|
||||
{{ form_widget(formChild) }}
|
||||
{% endfor %}
|
||||
{{ form_end(multi_update_form) }}
|
||||
{% endif %}
|
||||
{% if route is not empty and entries is not null %}
|
||||
<p class="d-none d-sm-block m-0 text-muted multi_update_form_hide">
|
||||
{{ 'pagination'|trans({'%start%': entries.getCurrentPageOffsetStart(), '%end%': entries.getCurrentPageOffsetEnd(), '%total%': entries.getNbResults()}) }}
|
||||
</p>
|
||||
{% set options = { 'css_container_class': 'pagination m-0 ms-auto d-print-none' } %}
|
||||
{% if route is iterable %}
|
||||
{% set options = options|merge(route) %}
|
||||
{% else %}
|
||||
{% set options = options|merge({routeName: route}) %}
|
||||
{% endif %}
|
||||
{{ pagination(entries, options) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ form_end(multi_update_form) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if route is not empty and entries is not null%}
|
||||
<div class="navigation text-center no-print">
|
||||
{{ pagination(entries, { routeName: route, 'css_container_class': 'pagination' }) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro datatable_meta_column(entry, field) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set metaField = entry.metaField(field.name) %}
|
||||
{% if not metaField is null and metaField.value is not null and metaField.value is not empty %}
|
||||
{% set metaField = metaField.merge(field) %}
|
||||
{{ widgets.form_type_value(metaField.type, metaField.value, entry) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro datatable_multiupdate_all() %}
|
||||
<input type="checkbox" id="multi_update_all" class="multiupdater">
|
||||
{% from "macros/widgets.html.twig" import meta_field_value %}
|
||||
{{ meta_field_value(entry, field) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro datatable_multiupdate_row(id) %}
|
||||
<input type="checkbox" name="id" value="{{ id }}" class="multi_update_single multiupdater">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro data_table_header(name, columns, skipStripped, reloadEvent) %}
|
||||
{% deprecated 'The macro "data_table_header()" is deprecated since 1.3 and will be removed with 2.0, use "datatable_header()" instead' %}
|
||||
{{ _self.data_table_header_options(name, columns, {'skipStripped': skipStripped, 'reloadEvent': reloadEvent}) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro data_table_header_options(name, columns, options) %}
|
||||
{% deprecated 'The macro "data_table_header_options()" is deprecated since 1.3 and will be removed with 2.0, use "datatable_header()" instead' %}
|
||||
{% set skipStripped = options.skipStripped|default(true) %}
|
||||
{% set reloadEvent = options.reloadEvent|default('') %}
|
||||
{% set translationDomain = options.translationDomain|default('messages') %}
|
||||
{# |default does not work here, as the prefix might be an empty string #}
|
||||
{% set translationPrefix = 'label.' %}
|
||||
{% if options.translationPrefix is defined %}
|
||||
{% set translationPrefix = options.translationPrefix %}
|
||||
{% endif %}
|
||||
|
||||
<div class="box box-{{ admin_lte_context.widget.type }} data_table" id="datatable_{{ name }}">
|
||||
<div class="box-body no-padding">
|
||||
<div class="dataTables_wrapper form-inline dt-bootstrap">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<table class="table {% if not skipStripped %}table-striped {% endif %}table-hover dataTable" role="grid" data-reload-event="{{ reloadEvent }}">
|
||||
<thead>
|
||||
<tr>
|
||||
{%- for title, class in columns -%}
|
||||
<th data-field="{{ title }}" class="{{ _self.data_table_column_class(name, columns, title) }}">{% if title is not empty and title != 'actions' %}{{ (translationPrefix ~ title)|trans({}, translationDomain) }}{% endif %}</th>
|
||||
{%- endfor -%}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<input type="checkbox" name="id" value="{{ id }}" class="multi_update_single multiupdater form-check-input m-0 align-middle">
|
||||
{% endmacro %}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
{% set percentDecimals = options['decimals'] ?? 1 %}
|
||||
{% set transPercentOpen = options['trans_percent_open'] ?? 'stats.percentUsedLeft' %}
|
||||
{% set transPercentSpent = options['trans_percent_spent'] ?? 'stats.percentUsed' %}
|
||||
{% set outerClass = options['class'] ?? '' %}
|
||||
{% if formatter == 'duration' %}
|
||||
{% set currentFormatted = current|duration %}
|
||||
{% set maxFormatted = max|duration %}
|
||||
@@ -31,12 +32,12 @@
|
||||
{% set width = 100 %}
|
||||
{% endif %}
|
||||
|
||||
<div class="progress-group">
|
||||
<div class="progress-group {{ outerClass }}">
|
||||
<div class="progress-title">
|
||||
<span class="progress-text">
|
||||
{{ currentFormatted }}
|
||||
</span>
|
||||
<span class="progress-number">
|
||||
<span class="progress-number text-end">
|
||||
<strong>
|
||||
{{ maxFormatted }}
|
||||
</strong>
|
||||
@@ -70,7 +71,7 @@
|
||||
{{ title }}
|
||||
{% if percentReached >= 0 and subTitle is not empty %} – {{ percentReached|number_format(2) }}%{% endif %}
|
||||
</span>
|
||||
<span class="progress-number">
|
||||
<span class="progress-number text-end">
|
||||
{% if percentReached >= 0 and subTitle is empty %}
|
||||
{{ percentReached|number_format(2) }}%
|
||||
{% elseif subTitle is not empty %}
|
||||
|
||||
@@ -1,67 +1,23 @@
|
||||
{% macro searchModal(form) %}
|
||||
{% form_theme form 'form/search-form.html.twig' %}
|
||||
<div class="modal fade" id="modal_search" tabindex="-1" role="dialog" aria-labelledby="modal_search_label">
|
||||
<div class="modal-dialog" role="document">
|
||||
{{ form_start(form, {'attr': {'class': 'form-narrow searchform'}}) }}
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="{{ 'action.close'|trans }}"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="modal_search_label">
|
||||
{{ 'search'|trans }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% set orderHasError = form.orderBy.vars.errors|length > 0 or form.order.vars.errors|length > 0 %}
|
||||
{% set orderBy = form_widget(form.orderBy) %}
|
||||
{% set order = form_widget(form.order) %}
|
||||
{{ form_rest(form) }}
|
||||
<div class="form-group{% if orderHasError %} has-error{% endif %}">
|
||||
{{ form_label(form.orderBy) }}
|
||||
<div class="col-sm-5 col-xs-5">
|
||||
{{ orderBy|raw }}
|
||||
{{ form_errors(form.orderBy) }}
|
||||
</div>
|
||||
<div class="col-sm-4 col-xs-3">
|
||||
{{ order|raw }}
|
||||
{{ form_errors(form.order) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="btn-toolbar pull-left" role="toolbar">
|
||||
{{ _self.searchButton(form) }}
|
||||
</div>
|
||||
<button type="button" class="btn btn-default btn-cancel" data-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro searchButton(form) %}
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('search') }}">
|
||||
<div class="btn-group">
|
||||
<button type="submit" name="performSearch" value="performSearch" class="btn btn-primary pull-left" data-type="submit">{{ 'search'|trans }}</button>
|
||||
</div>
|
||||
{% if form.vars.data.countFilter() > 0 and not form.vars.data.isBookmarkSearch() %}
|
||||
<div class="btn-group">
|
||||
<button type="submit" id="resetSearchFilter" name="resetSearchFilter" class="btn btn-warning" title="{{ 'action.reset'|trans }}">{{ 'action.reset'|trans }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="btn-group">
|
||||
<div class="btn-group me-auto">
|
||||
{% if form.vars.data.bookmark %}
|
||||
<button type="submit" id="setDefaultQuery" name="setDefaultQuery" class="btn btn-default" title="{{ 'label.set_as_default'|trans }}"><i class="{{ 'bookmarked'|icon }}"></i></button>
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<button type="submit" id="setDefaultQuery" name="setDefaultQuery" class="btn btn-outline-secondary" data-toggle="tooltip" title="{{ 'set_as_default'|trans }}">
|
||||
{{ icon('bookmarked', true) }}
|
||||
{{ 'action.save'|trans }}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="?_token={{ csrf_token('search') }}&removeDefaultQuery={{ form.vars.data.bookmark.name }}" id="removeDefaultQuery">{{ 'label.remove_default'|trans }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<a class="btn btn-icon btn-outline-warning" href="?_token={{ csrf_token('search') }}&removeDefaultQuery={{ form.vars.data.bookmark.name }}" data-toggle="tooltip" title="{{ 'remove_default'|trans }}" id="removeDefaultQuery">
|
||||
{{ icon('delete', true) }}
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="submit" id="setDefaultQuery" name="setDefaultQuery" class="btn btn-default" title="{{ 'label.set_as_default'|trans }}"><i class="{{ 'bookmark'|icon }}"></i></button>
|
||||
<button type="submit" id="setDefaultQuery" name="setDefaultQuery" class="btn btn-outline-secondary" data-toggle="tooltip" title="{{ 'set_as_default'|trans }}">
|
||||
{{ icon('bookmark', true) }}
|
||||
{{ 'action.save'|trans }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if form.vars.data.countFilter() > 0 and not form.vars.data.isBookmarkSearch() %}
|
||||
<button type="submit" id="resetSearchFilter" name="resetSearchFilter" class="btn btn-icon btn-outline-secondary" data-toggle="tooltip" title="{{ 'remove_filter'|trans }}">{{ icon('cancel', true) }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button type="submit" name="performSearch" value="performSearch" class="performSearch btn btn-primary ms-auto" data-type="submit">{{ 'search'|trans }}</button>
|
||||
{% endmacro %}
|
||||
|
||||
18
templates/macros/status.html.twig
Normal file
18
templates/macros/status.html.twig
Normal file
@@ -0,0 +1,18 @@
|
||||
{% macro status(title, color) %}
|
||||
{% from '@theme/components/status.html.twig' import status %}
|
||||
{% set options = {} %}
|
||||
{% if color %}{% set options = options|merge({'color': color}) %}{% endif %}
|
||||
{{ status(title, options) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro status_duration(duration) %}
|
||||
{{ _self.status(duration, 'azure') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro status_money(money) %}
|
||||
{{ _self.status(money, 'blue') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro status_count(counter) %}
|
||||
{{ _self.status(counter, 'teal') }}
|
||||
{% endmacro %}
|
||||
@@ -1,36 +0,0 @@
|
||||
{% macro toolbar(form, collapsible, displayInitial) %}
|
||||
{% deprecated 'The macro "toolbar" is deprecated since 1.2 and will be removed with 2.0, use search.searchModal() instead' %}
|
||||
<div class="toolbar no-print">
|
||||
{% if collapsible %}<div class="collapse{% if displayInitial %} collapse.show in{% endif %}" id="{{ collapsible }}">{% endif %}
|
||||
{{ form_start(form, { 'attr': {'class': 'navbar-form'}}) }}
|
||||
{{ form_widget(form) }}
|
||||
{{ form_end(form) }}
|
||||
{% if collapsible %}</div>{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro dropDownSearch(form) %}
|
||||
{% deprecated 'The macro "dropDownSearch" is deprecated since 1.4 and will be removed with 2.0, use search.searchModal() instead' %}
|
||||
<div class="modal fade" id="modal_search" tabindex="-1" role="dialog" aria-labelledby="modal_search_label">
|
||||
<div class="modal-dialog" role="document">
|
||||
{% form_theme form 'form/search-form.html.twig' %}
|
||||
{{ form_start(form, {'attr': {'class': 'form-narrow searchform'}}) }}
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="{{ 'action.close'|trans }}"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="modal_search_label">
|
||||
{{ 'search'|trans }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ form_rest(form) }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary pull-left" data-type="submit">{{ 'search'|trans }}</button>
|
||||
<button type="button" class="btn btn-default btn-cancel" data-dismiss="modal">{{ 'action.close'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,30 +1,13 @@
|
||||
{% macro init_frontend_loader() %}
|
||||
{% set configurations = javascript_configurations(app.user)|merge({
|
||||
login: path('fos_user_security_login'),
|
||||
login: path('login'),
|
||||
locale: app.request.locale,
|
||||
direction: tabler_bundle.rightToLeft ? 'rtl' : 'ltr',
|
||||
first_dow_iso: iso_day_by_name(app.user.firstDayOfWeek),
|
||||
autoComplete: kimai_config.themeAutocompleteCharacters,
|
||||
}) %}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function() {
|
||||
const loader = new KimaiWebLoader({
|
||||
{%- for name, value in configurations -%}
|
||||
{%- if value is same as (true) -%}
|
||||
{{ name }}:true
|
||||
{%- elseif value is same as (false) -%}
|
||||
{{ name }}:false
|
||||
{%- elseif value is number -%}
|
||||
{{ name }}:{{ value }}
|
||||
{%- else -%}
|
||||
{{ name }}:'{{ value }}'
|
||||
{%- endif -%}
|
||||
{%- if not loop.last %},{% endif -%}
|
||||
{%- endfor -%}
|
||||
},{
|
||||
{%- for key, translation in javascript_translations() -%}
|
||||
'{{ key }}': '{{ translation.0|trans({}, translation.1)|escape('js') }}'{% if not loop.last %},{% endif %}
|
||||
{%- endfor -%}
|
||||
});
|
||||
const loader = new KimaiWebLoader({{ configurations|json_encode|raw }},{{ javascript_translations()|json_encode|raw }});
|
||||
window.kimai = loader.getKimai();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,29 +1,55 @@
|
||||
{% macro icon(icon) %}
|
||||
<i class="{{ icon|icon(icon) }}"></i>
|
||||
{% endmacro %}
|
||||
|
||||
{%- macro page_actions(tools) -%}
|
||||
<div class="breadcrumb">
|
||||
<div class="box-tools">
|
||||
{{ _self.button_group(tools) }}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro entity_actions(tools) -%}
|
||||
<div class="breadcrumb">
|
||||
<div class="box-tools">
|
||||
<div class="btn-group">
|
||||
{% set actions = {} %}
|
||||
{%- for icon, values in tools %}
|
||||
{% if icon == 'back' %}
|
||||
{{ _self.action_button(icon, values) }}
|
||||
{%- macro page_actions(actions) -%}
|
||||
{% set large = true %}
|
||||
{% set btnClasses = 'btn-primary' %}
|
||||
{% set helpClasses = '' %}
|
||||
{% if tabler_bundle.isNavbarOverlapping() %}
|
||||
{% set helpClasses = 'btn-dark' %}
|
||||
{% elseif not tabler_bundle.isDarkMode() %}
|
||||
{% set btnClasses = 'btn-white' %}
|
||||
{% endif %}
|
||||
{% set later = {} %}
|
||||
{% set help = null %}
|
||||
<div class="page-actions">
|
||||
<div class="pa-desktop d-none d-sm-inline-flex btn-list">
|
||||
{%- apply spaceless -%}
|
||||
{%- for icon, values in actions %}
|
||||
{% if 'help' in icon %}
|
||||
{% set help = values %}
|
||||
{% elseif 'divider' in icon and values is null %}
|
||||
{# what to do here ? #}
|
||||
{% else %}
|
||||
{% set actions = actions|merge({(icon): values}) %}
|
||||
{% if values.children is defined and values.children|length > 0 %}
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn {{ btnClasses}} dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ icon(icon, true) }}{% if large %} {{ values.title is defined ? values.title|trans : icon|trans({}, 'actions') }}{% endif %}
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
{%- for icon, values in values.children %}
|
||||
{% set values = values|merge({class: 'dropdown-item action-' ~ icon ~ ' ' ~ (values.class|default(''))}) %}
|
||||
{{ _self.action_button(false, values, false) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{% set values = values|merge({combined: large, class: btnClasses ~ ' action-' ~ icon ~ ' ' ~ (values.class|default(''))}) %}
|
||||
{{ _self.action_button(icon, values) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ _self.table_actions(actions, '') }}
|
||||
</div>
|
||||
{% endfor -%}
|
||||
{% endapply %}
|
||||
{% if help is not null %}
|
||||
{% set help = help|merge({class: helpClasses ~ ' action-help ' ~ (help.class|default(''))}) %}
|
||||
{{ _self.action_button('help', help) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pa-mobile d-inline-flex d-sm-none btn-list">
|
||||
{% set btnClass = 'btn btn-icon ' %}
|
||||
{% if tabler_bundle.isNavbarOverlapping() %}
|
||||
{% set btnClass = 'btn btn-icon btn-dark' %}
|
||||
{% elseif not tabler_bundle.isDarkMode() %}
|
||||
{% set btnClass = 'btn btn-icon btn-white' %}
|
||||
{% endif %}
|
||||
{{ _self.table_actions(actions, btnClass) }}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
@@ -70,35 +96,43 @@
|
||||
{{ _self.label_color(team.name, team.color|colorize(team.name)) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user_avatar(user, tooltip, class) %}
|
||||
{% macro user_avatar(user, tooltip, class, badge) %}
|
||||
{% set avatar = null %}
|
||||
{% if user.avatar is not empty and kimai_config.themeAllowAvatarUrls %}
|
||||
{% set avatar = asset(user.avatar, 'avatars') %}
|
||||
{% if tooltip is same as (false) %}
|
||||
<img src="{{ avatar }}" class="img-circle avatar{% if class is not empty %} {{ class }}{% endif %}" alt="{{ user.displayName }}" />
|
||||
{% else %}
|
||||
<img src="{{ avatar }}" class="img-circle avatar{% if class is not empty %} {{ class }}{% endif %}" alt="{{ user.displayName }}" data-toggle="tooltip" data-placement="top" title="{{ tooltip|default(user.displayName) }}" />
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set color = user.color|colorize(user.displayName) %}
|
||||
<span class="avatar {{ class }}" style="background-color: {{ color }}; color: {{ color|font_contrast }}"
|
||||
{%- if tooltip is not same as (false) %} data-toggle="tooltip" data-placement="top" title="{{ tooltip|default(user.displayName) }}"{% endif -%}>
|
||||
<span class="initials">{{ user.initials }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if not user.enabled %}
|
||||
{% set badge = 'danger' %}
|
||||
{% endif %}
|
||||
{% set color = user.color|colorize(user.displayName) %}
|
||||
<span class="avatar {{ class }}"
|
||||
{%- if tooltip is not same as (false) %} data-toggle="tooltip" data-placement="top" title="{{ tooltip|default(user.displayName) }}"{% endif -%}
|
||||
{% if avatar is not null %}
|
||||
style="background-image: url({{ avatar }})">
|
||||
{% else %}
|
||||
style="background-color: {{ color }}; color: {{ color|font_contrast }}">{{ user.initials }}
|
||||
{% endif %}
|
||||
{%- if badge is not null -%}<span class="badge bg-{{ badge }}"></span>{%-endif -%}
|
||||
</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro avatar(initials, color, tooltip, class) %}
|
||||
{% set color = color|colorize(initials) %}
|
||||
<span class="avatar {{ class ?? '' }}" style="background-color: {{ color }}; color: {{ color|font_contrast }}"
|
||||
{%- if tooltip is not same as null %} data-toggle="tooltip" data-placement="top" title="{{ tooltip }}"{% endif -%}>{{ initials }}</span>
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
options = {'inherit': true, 'random': false}
|
||||
options = {'inherit': true}
|
||||
#}
|
||||
{% macro label_activity(activity, options) %}
|
||||
{% set inherit = options.inherit ?? true %}
|
||||
{% set random = options.random ?? false %}
|
||||
{% set isVisible = activity.visible %}
|
||||
{% set color = activity.color %}
|
||||
{% if color is empty and inherit and activity.project is not null %}
|
||||
{% set color = activity.project.color ?? activity.project.customer.color %}
|
||||
{% endif %}
|
||||
{% if color is empty and random %}
|
||||
{% if color is empty %}
|
||||
{% set color = color|colorize(activity.name) %}
|
||||
{% endif %}
|
||||
{% if isVisible and not activity.project is null %}
|
||||
@@ -111,11 +145,10 @@
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
options = {'inherit': true, 'random': false}
|
||||
options = {'inherit': true}
|
||||
#}
|
||||
{% macro label_project(project, options) %}
|
||||
{% set inherit = options.inherit ?? true %}
|
||||
{% set random = options.random ?? false %}
|
||||
{% set isVisible = false %}
|
||||
{% if project.visible and project.customer.visible %}
|
||||
{% set isVisible = true %}
|
||||
@@ -124,19 +157,15 @@
|
||||
{% if color is empty and inherit %}
|
||||
{% set color = project.customer.color %}
|
||||
{% endif %}
|
||||
{% if color is empty and random %}
|
||||
{% if color is empty %}
|
||||
{% set color = color|colorize(project.name) %}
|
||||
{% endif %}
|
||||
{{ _self.label_color_dot('project', isVisible, project.name, null, color) }}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
options = {'random': false}
|
||||
#}
|
||||
{% macro label_customer(customer, options) %}
|
||||
{% set random = options.random ?? false %}
|
||||
{% macro label_customer(customer) %}
|
||||
{% set color = customer.color %}
|
||||
{% if color is empty and random %}
|
||||
{% if color is empty %}
|
||||
{% set color = color|colorize(customer.name) %}
|
||||
{% endif %}
|
||||
{{ _self.label_color_dot('customer', customer.visible, customer.name, null, color) }}
|
||||
@@ -155,27 +184,15 @@
|
||||
{# for @internal use only #}
|
||||
{% macro label_color_dot(type, isVisible, name, url, color) %}
|
||||
{% apply spaceless %}
|
||||
<span class="label-{{ type }}{{ isVisible is same as (false) ? ' label-invisible' : '' }}">
|
||||
<span class="name">
|
||||
<i class="dot {{ 'dot'|icon }} fa-fw"{% if color is not empty %} style="color:{{ color }}"{% endif %}></i>
|
||||
<span class="name-inner">
|
||||
{%- if url is not empty -%}
|
||||
<a href="{{ url }}">{{ name }}</a>
|
||||
{%- else -%}
|
||||
{{ name }}
|
||||
{%- endif -%}
|
||||
</span>
|
||||
</span>
|
||||
{%- if url is not empty -%}<a href="{{ url }}">{% endif %}
|
||||
<span class="pe-1 label-{{ type }}{{ isVisible is same as (false) ? ' label-invisible' : '' }}">
|
||||
<span class="badge me-1" {% if color is not empty %} style="background-color:{{ color }}"{% endif %}></span>
|
||||
{{- name -}}
|
||||
</span>
|
||||
{%- if url is not empty -%}</a>{% endif %}
|
||||
{% endapply %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro color_dot(color, tooltip) %}
|
||||
<span class="label-color" data-toggle="tooltip" data-placement="top" title="{{ tooltip }}">
|
||||
<i class="dot {{ 'dot'|icon }} fa-fw"{% if color is not empty %} style="color:{{ color }}"{% endif %}></i>
|
||||
</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro badge_team_access(teams) %}
|
||||
{% if teams|length > 0 %}
|
||||
{{ _self.badge_counter(teams|length) }}
|
||||
@@ -193,268 +210,63 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro label(title, type, tooltip) %}
|
||||
<span {% if tooltip %}data-toggle="tooltip" data-placement="top" title="{{ tooltip }}" {% endif %}class="label label-{{ type|default('success') }}">{{ title }}</span>
|
||||
<span {% if tooltip %}data-toggle="tooltip" data-placement="top" title="{{ tooltip }}" {% endif %}class="badge bg-{{ type|default('success') }}">{{ title }}</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro label_color(title, color) %}
|
||||
<span class="label" style="background-color: {{ color|default_color }};color: {{ color|default_color|font_contrast }}">{{ title }}</span>
|
||||
<span class="badge" style="background-color: {{ color|default_color }};color: {{ color|default_color|font_contrast }}">{{ title }}</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro badge(title, color) %}
|
||||
<span class="badge"{% if color is not null %} style="background-color:{{ color }}; color:{{ color|font_contrast }}"{% endif %}>{{ title }}</span>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro alert(type, description, title, icon) %}
|
||||
<div class="alert alert-{{ type|default('danger') }} alert-dismissible">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
{% if title and icon %}
|
||||
<h4><i class="icon {{ icon|icon(icon) }}"></i> {{ title|trans }}</h4>
|
||||
{{ description|trans }}
|
||||
{% elseif icon %}
|
||||
<i class="icon {{ icon|icon(icon) }}"></i> {{ description|trans }}
|
||||
{% else %}
|
||||
{{ description|trans }}
|
||||
{% macro form_errors(form) %}
|
||||
{% if form.vars.errors|length > 0 %}
|
||||
{% set message = '' %}
|
||||
{% for error in form.vars.errors %}
|
||||
{% set message = message ~ error.message %}
|
||||
{% if not loop.last %}
|
||||
{% set message = message ~ '<br>' %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if message != '' %}
|
||||
{{ _self.alert('danger', message) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro alert(type, description, title, icon, dismissible) %}
|
||||
{% from '@theme/components/alert.html.twig' import alert %}
|
||||
{{ alert({type: type, description: description|trans, title: title|trans, icon: icon, dismissible: dismissible}) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro callout(type, description, title, icon) %}
|
||||
<div class="callout callout-{{ type|default('danger') }} lead">
|
||||
{% if title %}<h4>{% if icon %}<i class="{{ icon|icon(icon) }}">{% endif %}</i> {{ title|trans }}</h4>{% endif %}
|
||||
<p>{{ description|trans }}</p>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro info_box_progress(title, description, amount, percentage, icon, color) %}
|
||||
<div class="info-box bg-{{ color }}">
|
||||
<span class="info-box-icon"><i class="{{ icon|icon(icon) }}"></i></span>
|
||||
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">{{ title|trans }}</span>
|
||||
<span class="info-box-number">{{ amount }}</span>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar" style="width: {{ percentage }}%"></div>
|
||||
</div>
|
||||
<span class="progress-description">
|
||||
{{ description }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro button_group_dropdown(title, actions) %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default">{{ title|trans }}</button>
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">{{ 'label.toggle_dropdown'|trans }}</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
{% for url, entry in actions %}
|
||||
<li><a href="{{ url }}">{{ entry|trans }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% from '@theme/components/alert.html.twig' import alert %}
|
||||
{{ alert({type: type|default('danger'), description: description|trans, title: title|trans, icon: icon, dismissible: false, important: true}) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro table_actions(actions, class) %}
|
||||
{% if actions|length >= 1 %}
|
||||
{% 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 %}
|
||||
{% if 'divider' in icon 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 %}
|
||||
{% if values['title'] is not defined %}
|
||||
{% set values = values|merge({'title': icon|trans({}, 'actions')}) %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set values = {'url': values, 'title': icon|trans({}, 'actions')} %}
|
||||
{% endif %}
|
||||
{% if icon == 'trash' %}
|
||||
{% set trash = values %}
|
||||
{% else %}
|
||||
{% set divider = false %}
|
||||
{% if values.children is defined %}
|
||||
{%- for childIcon, childValues in values.children %}
|
||||
<li>
|
||||
{{ _self.action_button(childIcon, childValues, false) }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<li>
|
||||
{{ _self.action_button(icon, values, false) }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor -%}
|
||||
{%- if trash is not null %}
|
||||
{% if actions|length > 1 and divider is same as (false) %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li class="delete">
|
||||
{{ _self.action_button('trash', trash, false) }}
|
||||
</li>
|
||||
{% endif -%}
|
||||
{% endapply %}
|
||||
</ul>
|
||||
</div>
|
||||
{% import '@theme/components/actions.html.twig' as macro %}
|
||||
{{ macro.actions(actions, {'class': class|default('btn-sm'), translationDomain: 'actions'}) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro list_group_actions(actions, highlightUrl) %}
|
||||
{% if actions|length > 0 %}
|
||||
<div class="list-group">
|
||||
{% for icon, values in actions %}
|
||||
{% if 'divider' in icon and values is null %}
|
||||
</div><div class="list-group">
|
||||
{# <a class="list-group-item disabled list-group-divider"></a> #}
|
||||
{% else %}
|
||||
{% if values is not iterable %}
|
||||
{% set values = {'url': values} %}
|
||||
{% endif %}
|
||||
{% if values['children'] is defined %}
|
||||
{{ _self.list_group_actions(values['children'], highlightUrl) }}
|
||||
{% else %}
|
||||
{% if values['title'] is not defined %}
|
||||
{% set values = values|merge({'title': icon|trans({}, 'actions')}) %}
|
||||
{% endif %}
|
||||
{% set class = 'list-group-item' %}
|
||||
{% if highlightUrl == values.url %}
|
||||
{% set class = class ~ ' active' %}
|
||||
{% endif %}
|
||||
{% set values = values|merge({'class': class}) %}
|
||||
{{ _self.action_button(icon, values, false) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% macro card_tool_visibility(tableName) %}
|
||||
{{ _self.card_tool_button('visibility', {'modal': ('#modal_' ~ tableName)}) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro card_tool_button(icon, values) %}
|
||||
{% import '@theme/components/buttons.html.twig' as macro %}
|
||||
{% set values = values|merge({'tooltip_attr': 'data-toggle'}) %}
|
||||
{{ macro.action_cardtoolbutton(icon, values) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro action_button(icon, values, type) %}
|
||||
{%- apply spaceless -%}
|
||||
{% set id = null %}
|
||||
{% set onclick = null %}
|
||||
{% set modal = null %}
|
||||
{% set toggle = null %}
|
||||
{% set url = null %}
|
||||
{% set target = null %}
|
||||
{% set title = null %}
|
||||
{% set disabled = false %}
|
||||
{% set attr = {} %}
|
||||
{% set label = null %}
|
||||
{% set accesskey = null %}
|
||||
{% set translation_domain = 'messages' %}
|
||||
{% if type is same as (false) %}
|
||||
{% set class = "" %}
|
||||
{% elseif type is null %}
|
||||
{% set class = "btn btn-default btn-" ~ icon ~ " " %}
|
||||
{% else %}
|
||||
{% set class = "btn btn-" ~ type ~ " btn-" ~ icon ~ " " %}
|
||||
{% endif %}
|
||||
|
||||
{% if values is not iterable %}
|
||||
{% deprecated 'Passing a non iterable action_button is deprecated since 1.15 and will be removed with 2.0' %}
|
||||
{% set url = values %}
|
||||
{% if 'onclick:' in url %}
|
||||
{% set onclick = url|replace({'onclick:': ''}) %}
|
||||
{% set url = '#' %}
|
||||
{% endif %}
|
||||
{% if '#collapse' in url %}
|
||||
{% set toggle = 'collapse' %}
|
||||
{% endif %}
|
||||
{% if '#modal' in url %}
|
||||
{% set modal = url %}
|
||||
{% set url = '#' %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set icon = values.icon ?? icon %}
|
||||
{% set disabled = values.disabled ?? false %}
|
||||
{% set url = values.url ?? '#' %}
|
||||
{% set onclick = values.onclick ?? null %}
|
||||
{% set modal = values.modal ?? null %}
|
||||
{% set toggle = values.toggle ?? null %}
|
||||
{% set target = values.target ?? null %}
|
||||
{% set id = values.id ?? null %}
|
||||
{% set title = values.title ?? null %}
|
||||
{% set class = class ~ ( values.class | default("")) %}
|
||||
{% set attr = values.attr ?? {} %}
|
||||
{% set label = values.label ?? null %}
|
||||
{% set translation_domain = values.translation_domain ?? translation_domain %}
|
||||
{% set accesskey = values.accesskey ?? null %}
|
||||
{% endif %}
|
||||
|
||||
{% if disabled is same as (true) %}
|
||||
{% set class = class ~ " disabled" %}
|
||||
{% endif %}
|
||||
|
||||
<a class="{{ class | trim }}" href="{{ url }}"
|
||||
{%- if disabled is same as (true) %} disabled="disabled"{% endif %}
|
||||
{%- if id is not empty %} id="{{ id }}"{% endif %}
|
||||
{%- if toggle is not empty %} data-toggle="{{ toggle }}"{% endif %}
|
||||
{%- if modal is not empty %} data-toggle="modal" data-target="{{ modal }}"{% endif %}
|
||||
{%- if onclick is not empty %} onclick="{{ onclick }}"{% endif %}
|
||||
{%- if target is not empty %} target="{{ target }}"{% endif %}
|
||||
{%- if accesskey is not empty %} accesskey="{{ accesskey }}"{% endif %}
|
||||
{%- if attr is not empty %}
|
||||
{% for name, value in attr %}
|
||||
{{ ' ' ~ name }}={% if '"' in value %}'{{ value|raw }}'{% else %}"{{ value|raw }}"{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
>{% if title is not null %}{{ title|trans({}, translation_domain) }}{% else %}{{ _self.icon(icon) }}{% endif %}{% if label is not null %}<span class="label bg-yellow">{{ label }}</span>{% endif %}</a>
|
||||
{% endapply %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro button_group(actions, type) %}
|
||||
<div class="btn-group">
|
||||
{%- apply spaceless -%}
|
||||
{%- for icon,values in actions %}
|
||||
{% if 'divider' in icon and values is null %}
|
||||
{# what to do here ? #}
|
||||
{% else %}
|
||||
{% if values.children is defined %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
{{ _self.icon(icon) }}
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">{{ 'label.toggle_dropdown'|trans }}</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
{% for childIcon,childValues in values.children %}
|
||||
<li>{{ _self.action_button(childIcon, childValues, false) }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ _self.action_button(icon, values, type) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor -%}
|
||||
{% endapply %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro button_action(icon, url) %}
|
||||
<a href="{{ url }}" class="btn btn-default btn-{{ icon }}">
|
||||
{{ _self.icon(icon) }}
|
||||
</a>
|
||||
{% import '@theme/components/button.html.twig' as macro %}
|
||||
{% set values = values|merge({'tooltip_attr': 'data-toggle'}) %}
|
||||
{{ macro.button(icon, values, type) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro tag_list(taglist) %}
|
||||
@@ -463,9 +275,21 @@
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro meta_field_value(entity, field) %}
|
||||
{% set metaField = entity.metaField(field.name) %}
|
||||
{% if not metaField is null and metaField.value is not null and metaField.value is not empty %}
|
||||
{% set metaField = metaField.merge(field) %}
|
||||
{{ _self.form_type_value(metaField.type, metaField.value, entity) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro form_type_value(type, value, entity) %}
|
||||
{% if '\\ColorPickerType' in type %}
|
||||
{{ _self.color_dot(value, value) }}
|
||||
{% if '\\ColorPickerType' in type or '\\ColorChoiceType' in type %}
|
||||
{% if value is not empty %}
|
||||
<span class="label-color" data-toggle="tooltip" data-placement="top" title="{{ value }}">
|
||||
<i class="dot {{ 'dot'|icon(true) }} fa-fw" style="color:{{ value }}"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% elseif '\\DurationType' in type %}
|
||||
{{ value|duration }}
|
||||
{% elseif '\\YesNoType' in type or '\\CheckBoxType' in type %}
|
||||
@@ -473,14 +297,20 @@
|
||||
{% elseif '\\DatePickerType' in type %}
|
||||
{{ value|date_short }}
|
||||
{% elseif '\\DateTimePickerType' in type %}
|
||||
{{ value|date_full }}
|
||||
{{ value|date_time }}
|
||||
{% elseif '\\CountryType' in type %}
|
||||
{{ value|country }}
|
||||
{{ value|country_name }}
|
||||
{% elseif '\\CurrencyType' in type %}
|
||||
{{ value|currency }}
|
||||
{{ value|currency_name }}
|
||||
{% elseif '\\LanguageType' in type %}
|
||||
{{ value|language }}
|
||||
{% elseif '\\MoneyType' in type %}
|
||||
{{ value|locale_name }}
|
||||
{% elseif '\\TagsType' in type %}
|
||||
{% for tag in value|split(',') %}
|
||||
{% if tag is not empty %}
|
||||
{{ _self.badge(tag, null|colorize(tag)) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% elseif '\\MoneyType' in type %}
|
||||
{% set classname = class_name(entity) %}
|
||||
{% if entity is null %}
|
||||
{{ value }}
|
||||
@@ -506,46 +336,35 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro team_list(teams, showTitle, collapseAt) %}
|
||||
{% set collapseAt = collapseAt ?? 12 %}
|
||||
{% if showTitle is null %}
|
||||
{% set showTitle = true %}
|
||||
{% endif %}
|
||||
{% macro team_list(teams, options) %}
|
||||
{% set showTitle = options['title'] ?? true %}
|
||||
{% set collapseAt = options['collapse'] ?? 12 %}
|
||||
{% set nameClass = options['name_class'] ?? '' %}
|
||||
{% set viewTeam = is_granted('view_team') %}
|
||||
<table class="table table-hover dataTable" role="grid">
|
||||
{% if showTitle %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="hw-min">{{ 'label.team'|trans }}</th>
|
||||
<th>{{ 'label.user'|trans }}</th>
|
||||
<th class="hw-min">{{ 'team'|trans }}</th>
|
||||
<th class="d-none d-sm-table-cell">{{ 'team.member'|trans({}, 'teams') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% endif %}
|
||||
<tbody>
|
||||
{% for team in teams|sort((a, b) => a.name <=> b.name) %}
|
||||
{% set members = team.members|filter(m => m.user.enabled) %}
|
||||
{% set userTeamCount = members|length %}
|
||||
<tr{% if is_granted('edit', team) %} class="modal-ajax-form open-edit" data-href="{{ path('admin_team_member', {'id': team.id}) }}"{% endif %}>
|
||||
<td>
|
||||
{% set class = 'avatar-rounded' %}
|
||||
<tr{% if viewTeam and is_granted('edit', team) %} class="modal-ajax-form open-edit" data-href="{{ path('admin_team_member', {'id': team.id}) }}"{% endif %}>
|
||||
<td class="{{ nameClass }}">
|
||||
{{ _self.label_color_dot('color', true, team.name, null, team.color|colorize(team.name)) }}
|
||||
</td>
|
||||
<td class="avatars">
|
||||
{% set teamHiddenId = 'team_' ~ team.id ~ '_hiddenUser' ~ random() %}
|
||||
{% set counter = 0 %}
|
||||
<td class="d-none d-sm-table-cell avatars avatar-list avatar-list-stacked">
|
||||
{% for member in members|sort((a, b) => b.teamlead <=> a.teamlead) %}
|
||||
{% set user = member.user %}
|
||||
{% if member.teamlead %}
|
||||
{{ _self.user_avatar(user, ('label.teamlead'|trans ~ ': ' ~ user.displayName), 'teamlead') }}
|
||||
{{ _self.user_avatar(user, ('teamlead'|trans ~ ': ' ~ user.displayName), (class ~ ' teamlead'), 'info') }}
|
||||
{% else %}
|
||||
{{ _self.user_avatar(user) }}
|
||||
{% endif %}
|
||||
{% set counter = counter + 1 %}
|
||||
{% if userTeamCount > (collapseAt + 1) and counter == (collapseAt - 1) and loop.index != userTeamCount %}
|
||||
<a href="#" onclick="$(this).parent().append($('#{{ teamHiddenId }}').contents());$('#{{ teamHiddenId }}').remove();$(this).remove();return false;" class="badge">{{ 'label.plus_more'|trans({'%count%': (userTeamCount - collapseAt)}) }}</a>
|
||||
<span class="hidden" id="{{ teamHiddenId }}">
|
||||
{% set counter = counter + 1 %}
|
||||
{% endif %}
|
||||
{% if userTeamCount > collapseAt and counter != (collapseAt - 1) and loop.index == userTeamCount %}
|
||||
</span>
|
||||
{{ _self.user_avatar(user, null, class) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
@@ -571,7 +390,7 @@
|
||||
<tr class="{{ class_customer_row(customer, now) }}">
|
||||
#}
|
||||
{% macro class_customer_row(customer, now) %}
|
||||
{%- if not customer.visible %}warning{% endif -%}
|
||||
{%- if not customer.visible %}bg-orange-lt{% endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
@@ -593,7 +412,7 @@
|
||||
<tr class="{{ class_project_row(project, now) }}">
|
||||
#}
|
||||
{% macro class_project_row(project, now) %}
|
||||
{%- if not project.visible or (project.end is not null and project.end < now) %}warning{% endif -%}
|
||||
{%- if not project.visible or (project.end is not null and project.end < now) %}bg-orange-lt{% endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
@@ -615,7 +434,7 @@
|
||||
<tr {{ class_activity_row(activity, now) }}>
|
||||
#}
|
||||
{% macro class_activity_row(activity, now) %}
|
||||
{%- if not activity.visible %}warning{% endif -%}
|
||||
{%- if not activity.visible %}bg-orange-lt{% endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
@@ -637,7 +456,7 @@
|
||||
<tr class="{{ class_user_row(user, now) }}">
|
||||
#}
|
||||
{% macro class_user_row(user) %}
|
||||
{%- if not user.enabled %}warning{% endif -%}
|
||||
{%- if not user.enabled %}bg-orange-lt{% endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
@@ -676,3 +495,11 @@
|
||||
{{ 0|number_format(2) }} %
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro page_reloader(events, full_reload) %}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
KimaiReloadPageWidget.create('{{ events }}', {% if full_reload is same as true %}true{% else %}false{% endif %});
|
||||
});
|
||||
</script>
|
||||
{% endmacro %}
|
||||
@@ -1,19 +0,0 @@
|
||||
{% if is_granted('start_own_timesheet') %}
|
||||
<li class="dropdown notifications-menu">
|
||||
<a href="#" class="dropdown-toggle ddt-small" data-toggle="dropdown">
|
||||
<i class="{{ 'repeat'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu"
|
||||
data-api="{{ path('recent_timesheet', {'size' : 10}) }}"
|
||||
data-href="{{ path('restart_timesheet', {'id' : '000'}) }}"
|
||||
data-icon="{{ 'start-small'|icon }}"
|
||||
data-template="{{ 'recent.activities.format'|trans }}">
|
||||
<li class="header">{{ 'recent.activities'|trans }}</li>
|
||||
<li>
|
||||
<ul class="menu">
|
||||
</ul>
|
||||
</li>
|
||||
<li class="footer"><a href="{{ path('timesheet') }}">{{ 'timesheet.all'|trans }}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -3,8 +3,8 @@
|
||||
<link rel="icon" type="image/png" href="{{ asset('favicon-32x32.png') }}" sizes="32x32">
|
||||
<link rel="apple-touch-icon" href="{{ asset('apple-touch-icon.png') }}">
|
||||
<link rel="manifest" href="{{ asset('manifest.json') }}">
|
||||
<meta name="apple-mobile-web-app-title" content="Kimai 2">
|
||||
<meta name="apple-mobile-web-app-title" content="{{ constant('App\\Constants::SOFTWARE') }}">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="Kimai 2">
|
||||
<meta name="application-name" content="{{ constant('App\\Constants::SOFTWARE') }}">
|
||||
<meta name="msapplication-config" content="{{ asset('browserconfig.xml') }}">
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
{% if logo is not empty %}
|
||||
<img src="{{ logo }}" style="max-width: 300px;">
|
||||
{% elseif company is not empty %}
|
||||
<b>{{ company|raw }}</b>
|
||||
<p>{{ company }}</p>
|
||||
{% elseif tabler_bundle.getLogoUrl() is not empty %}
|
||||
<img alt="{{ get_title() }}" style="max-width: 150px;"
|
||||
src="{% if '://' in tabler_bundle.getLogoUrl() %}{{ tabler_bundle.getLogoUrl() }}{% else %}{{ asset(tabler_bundle.getLogoUrl()) }}{% endif %}">
|
||||
{% else %}
|
||||
{{- get_title('<b>', '</b><br>')|raw -}}
|
||||
<p>{{ get_title('', '<br>')|raw }}</p>
|
||||
{% endif %}
|
||||
|
||||
51
templates/partials/recent-activities.html.twig
Normal file
51
templates/partials/recent-activities.html.twig
Normal file
@@ -0,0 +1,51 @@
|
||||
{% if is_granted('start_own_timesheet') %}
|
||||
{% set favorites = favorite_timesheets(app.user) %}
|
||||
{% if favorites|length > 0 %}
|
||||
{% from 'macros/widgets.html.twig' import label_customer, label_project, label_activity %}
|
||||
{% set class = "btn-outline-white" %}
|
||||
{% if tabler_bundle.isDarkMode() or tabler_bundle.isNavbarOverlapping() %}
|
||||
{% set class = "btn-dark" %}
|
||||
{% endif %}
|
||||
<div class="nav-item dropdown d-none d-md-flex me-3 notifications-menu" data-reload="{{ path('favorites_timesheets') }}">
|
||||
<a href="#" class="btn btn-icon {{ class}} px-0" data-bs-toggle="dropdown" tabindex="-1" aria-label="{{ 'recent.activities'|trans }}">
|
||||
{{ icon('repeat') }}
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-card">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ 'recent.activities'|trans }}</div>
|
||||
<div class="list-group list-group-flush list-group-hoverable menu">
|
||||
{% for favorite in favorites %}
|
||||
{% set entry = favorite.getTimesheet() %}
|
||||
<div class="list-group-item">
|
||||
<div class="row align-items-center">
|
||||
<div class="col text-truncate" >
|
||||
<a class="api-link text-decoration-none text-body d-block"
|
||||
href="{{ path('restart_timesheet', {'id': entry.id}) }}" data-event="kimai.timesheetStart kimai.timesheetUpdate" data-method="PATCH" data-msg-error="timesheet.start.error"
|
||||
data-msg-success="timesheet.start.success">
|
||||
{{ label_activity(entry.activity) }}
|
||||
<div class="d-block text-truncate mt-n1">
|
||||
{{ label_project(entry.project) }}
|
||||
{{ label_customer(entry.project.customer) }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
{% if favorite.isFavorite() %}
|
||||
<a href="{{ path('favorites_timesheets_remove', {'id': favorite.timesheet.id}) }}" class="list-group-item-actions show">
|
||||
<i class="text-yellow {{ 'bookmarked'|icon }}"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ path('favorites_timesheets_add', {'id': favorite.timesheet.id}) }}" class="list-group-item-actions">
|
||||
<i class="{{ 'bookmark'|icon }}"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
27
templates/partials/ticktack.html.twig
Normal file
27
templates/partials/ticktack.html.twig
Normal file
@@ -0,0 +1,27 @@
|
||||
{% set active_timesheets = active_timesheets(app.user) %}
|
||||
{% set hasActiveRecords = active_timesheets is not empty %}
|
||||
{% if not hasActiveRecords %}
|
||||
{# fake entry, because at least one html template node is needed #}
|
||||
{% set active_timesheets = [{'id': '000', 'begin': null, 'activity': {'name': ''}, 'project': {'name': '', 'customer': {'name': ''}}}] %}
|
||||
{% endif %}
|
||||
<div class="nav-item d-flex me-3">
|
||||
<div class="btn-list">
|
||||
{% set class = "btn-outline-white" %}
|
||||
{% if tabler_bundle.isDarkMode() or tabler_bundle.isNavbarOverlapping() %}
|
||||
{% set class = "btn-dark" %}
|
||||
{% endif %}
|
||||
{% set entry = active_timesheets[0] %}
|
||||
<div class="messages-menu" data-api="{{ path('active_timesheet') }}" data-href="{{ path('stop_timesheet', {'id' : '000'}) }}" style="{% if not hasActiveRecords %}display:none{% endif %}">
|
||||
<a data-replacer="url" class="api-link ticktac-running ticktac-stop btn {{ class }}" href="{{ path('stop_timesheet', {'id' : entry.id}) }}" data-event="kimai.timesheetStop kimai.timesheetUpdate" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success"{% if hasActiveRecords %} accesskey="s"{% endif %}>
|
||||
<i class="icon text-red {{ 'stop-small'|icon(true) }}"></i>
|
||||
<span data-replacer="duration" data-title="true" data-since="{{ entry.begin is null ? '' : entry.begin|date_format(constant('DATE_ISO8601')) }}">{{ entry is iterable ? 0|duration : entry|duration }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="messages-menu-empty" style="{% if hasActiveRecords %}display:none{% endif %}">
|
||||
<a href="{{ path('timesheet_create') }}" class="modal-ajax-form ticktac-start btn {{ class }}" accesskey="n">
|
||||
<i class="icon text-green {{ 'start'|icon(true) }}"></i>
|
||||
<span>{{ 0|duration }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +0,0 @@
|
||||
{% macro user_permissions(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'user_permissions', view) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
@@ -1,12 +1,8 @@
|
||||
{% 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 %}
|
||||
{% import "permission/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'user_role.title'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.user_permissions('role') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': (role.id ? 'action.edit'|trans : 'create'|trans) ~ ': ' ~ 'user_role.title'|trans,
|
||||
'form': form,
|
||||
'back': path('admin_user_permissions')
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "permission/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'label.name': 'alwaysVisible',
|
||||
} %}
|
||||
{% set canEditPermissions = is_granted('role_permissions') %}
|
||||
{% for role in roles %}
|
||||
{% set options = {'class': 'alwaysVisible text-center'} %}
|
||||
{% if canEditPermissions and role.name not in system_roles|keys %}
|
||||
{% set widget %}
|
||||
<a href="{{ path('admin_user_role_delete', {'id': role.id, 'csrfToken': token}) }}" class="confirmation-link" data-question="confirm.delete">{{ widgets.icon('trash') }}</a>
|
||||
{% endset %}
|
||||
{% set options = options|merge({'html_after': widget}) %}
|
||||
{% endif %}
|
||||
{% set columns = columns|merge({
|
||||
(role.name|trans): options,
|
||||
}) %}
|
||||
{% endfor %}
|
||||
|
||||
{% set tableName = 'user_admin_permissions' %}
|
||||
|
||||
{% block page_title %}{{ 'profile.roles'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.user_permissions('index') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ tables.datatable_header(tableName, columns, null, {'translationPrefix': ''}) }}
|
||||
|
||||
{% set roles = roles|reverse %}
|
||||
{% set columns = {
|
||||
'name': 'alwaysVisible',
|
||||
} %}
|
||||
{% set canEditPermissions = is_granted('role_permissions') %}
|
||||
{% for role in roles %}
|
||||
{% set class = 'alwaysVisible text-center' %}
|
||||
{% if role.name == 'ROLE_SUPER_ADMIN' %}
|
||||
{% set class = class ~ ' bg-orange-lt' %}
|
||||
{% elseif role.name == 'ROLE_USER' %}
|
||||
{% set class = class ~ ' bg-green-lt' %}
|
||||
{% endif %}
|
||||
{% set options = {'class': class} %}
|
||||
{% if canEditPermissions and (role.name not in system_roles) %}
|
||||
{% set widget %}
|
||||
<a href="{{ path('admin_user_role_delete', {'id': role.id, 'csrfToken': token}) }}" class="confirmation-link" data-question="confirm.delete">{{ icon('trash') }}</a>
|
||||
{% endset %}
|
||||
{% set options = options|merge({'html_after': widget}) %}
|
||||
{% endif %}
|
||||
{% set columns = columns|merge({
|
||||
(role.name): options,
|
||||
}) %}
|
||||
{% endfor %}
|
||||
|
||||
{% set tableName = 'user_admin_permissions' %}
|
||||
|
||||
{{ tables.datatable_header(tableName, columns, null, {'reload': 'kimai.userRoleUpdate'}) }}
|
||||
|
||||
{% set colspan = 1 + (roles|length) %}
|
||||
|
||||
@@ -41,7 +44,7 @@
|
||||
<td>{{ permission }}</td>
|
||||
{% for role in roles %}
|
||||
{% set value = manager.permission(role.name, permission) %}
|
||||
<td class="text-center">
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, role.name) }}">
|
||||
{# see RolePermissionManager for this special case #}
|
||||
{% if role.name == 'ROLE_SUPER_ADMIN' and permission in always_apply_superadmin %}
|
||||
{% if value %}
|
||||
@@ -61,55 +64,70 @@
|
||||
|
||||
{{ tables.data_table_footer() }}
|
||||
|
||||
<div style="display: none" id="permission-token" data-value="{{ token }}"></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
let PERM_TOKEN = '{{ token }}';
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
jQuery('a.togglePerm').on('click', function(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
function getPermissionToken() {
|
||||
return document.getElementById('permission-token').dataset['value'];
|
||||
}
|
||||
function setPermissionToken(token) {
|
||||
document.getElementById('permission-token').dataset['value'] = token;
|
||||
}
|
||||
|
||||
let target = event.target;
|
||||
if (!target.matches('a')) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
document.addEventListener('kimai.initialized', (event) => {
|
||||
const FETCHER = event.detail.kimai.getPlugin('fetch');
|
||||
for (const element of document.querySelectorAll('a.togglePerm')) {
|
||||
element.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
let linkElement = jQuery(target);
|
||||
linkElement.html('<i class="fas fa-spinner fa-pulse"></i>');
|
||||
|
||||
let isActive = linkElement.hasClass('permOn');
|
||||
let url = linkElement.data('href').replace(/__VALUE__/, isActive ? '0' : '1').replace(/__TOKEN__/, PERM_TOKEN);
|
||||
|
||||
jQuery.ajax({
|
||||
url: url,
|
||||
headers: {'Content-Type':'application/json'},
|
||||
method: 'POST',
|
||||
data: [], {# data doesn't matter, everything is in the URL #}
|
||||
dataType: 'json',
|
||||
success: function(result) {
|
||||
kimai.getPlugin('alert').success('action.update.success');
|
||||
linkElement.toggleClass('permOn').toggleClass('permOff');
|
||||
toggleLabel(linkElement, !isActive);
|
||||
PERM_TOKEN = result.token;
|
||||
},
|
||||
error: function(xhr, err) {
|
||||
kimai.getPlugin('alert').error('action.update.error');
|
||||
toggleLabel(linkElement, isActive);
|
||||
let target = event.target;
|
||||
if (!target.matches('a')) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
|
||||
let linkElement = target;
|
||||
linkElement.innerHTML = '{{ icon('spinner') }}';
|
||||
|
||||
const isActive = linkElement.classList.contains('permOn');
|
||||
let url = linkElement.dataset['href'].replace(/__VALUE__/, isActive ? '0' : '1').replace(/__TOKEN__/, getPermissionToken());
|
||||
|
||||
/** @type {KimaiFetch} FETCHER */
|
||||
const headers = new Headers();
|
||||
headers.append('Content-Type', 'application/json');
|
||||
|
||||
const options = {method: 'POST', headers: headers};
|
||||
|
||||
FETCHER.fetch(url, options)
|
||||
.then(response => {
|
||||
response.json().then(json => {
|
||||
kimai.getPlugin('alert').success('action.update.success');
|
||||
linkElement.classList.toggle('permOn');
|
||||
linkElement.classList.toggle('permOff');
|
||||
toggleLabel(linkElement, !isActive);
|
||||
setPermissionToken(json.token);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
kimai.getPlugin('alert').error('action.update.error');
|
||||
toggleLabel(linkElement, isActive);
|
||||
})
|
||||
;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
KimaiReloadPageWidget.create('kimai.userRoleUpdate');
|
||||
});
|
||||
|
||||
function toggleLabel(element, showTrue) {
|
||||
if (showTrue) {
|
||||
element.html('{{ widgets.label_boolean(true)|e('js') }}');
|
||||
element.innerHTML = '{{ widgets.label_boolean(true)|trim|e('js') }}';
|
||||
} else {
|
||||
element.html('{{ widgets.label_boolean(false)|e('js') }}');
|
||||
element.innerHTML = '{{ widgets.label_boolean(false)|trim|e('js') }}';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,57 +2,85 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
|
||||
{% block page_title %}{{ 'menu.plugin'|trans }}{% endblock %}
|
||||
{% block page_actions %}
|
||||
{% set event = actions(app.user, 'plugins', 'index') %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if plugins|length == 0 %}
|
||||
{{ widgets.callout('warning', 'plugin.none_installed'|trans({}, 'plugins')) }}
|
||||
{% else %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% block box_title %}{{ 'plugin.installed'|trans }}{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set columns = {
|
||||
'name': {'class': 'alwaysVisible'},
|
||||
'description': {'class': 'd-none d-md-table-cell'},
|
||||
'version': {'class': 'text-center w-min'},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
} %}
|
||||
|
||||
{% set columns = {
|
||||
'name': {'class': 'alwaysVisible'},
|
||||
'version': {'class': ' w-min'},
|
||||
'description': {'class': 'hidden-xs'},
|
||||
'required_version': {'class': 'hidden-xs hidden-sm w-min'},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
} %}
|
||||
{% set tableName = 'plugins' %}
|
||||
|
||||
{% set tableName = 'plugins' %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{{ tables.datatable_header(tableName, columns, null, {columnConfig: false}) }}
|
||||
{% for plugin in plugins|sort((p1, p2) => p1.name|lower <=> p2.name|lower) %}
|
||||
<tr>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
|
||||
{% if plugin.id != plugin.name %}
|
||||
<span data-toggle="tooltip" data-placement="top" title="{{ plugin.id }}">{{ plugin.name }}</span>
|
||||
{% else %}
|
||||
{{ plugin.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}">{{ plugin.metadata.description }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'version') }}">{{ widgets.label(plugin.metadata.version, 'primary') }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
|
||||
{% set event = actions(app.user, 'plugin', 'index', {'plugin': plugin}) %}
|
||||
{{ widgets.table_actions(event.actions) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{{ tables.data_table_footer(plugins) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endif %}
|
||||
|
||||
{{ tables.datatable_header(tableName, columns, null, {}) }}
|
||||
{% for plugin in plugins|sort((p1, p2) => p1.name|lower <=> p2.name|lower) %}
|
||||
<tr>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
|
||||
{% if plugin.id != plugin.name %}
|
||||
<span data-toggle="tooltip" data-placement="top" title="{{ plugin.id }}">{{ plugin.name }}</span>
|
||||
{% else %}
|
||||
{{ plugin.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'version') }}">{{ widgets.label(plugin.metadata.version, 'primary') }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}">{{ plugin.metadata.description }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'required_version') }}">
|
||||
{% if plugin.metadata.kimaiVersion > constant('App\\Constants::VERSION') %}
|
||||
{{ widgets.label(plugin.metadata.kimaiVersion, 'danger') }}
|
||||
{% elseif plugin.metadata.kimaiVersion < constant('App\\Constants::VERSION') %}
|
||||
{{ widgets.label(plugin.metadata.kimaiVersion, 'warning') }}
|
||||
{% else %}
|
||||
{{ widgets.label(plugin.metadata.kimaiVersion, 'success') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
|
||||
{% set event = actions(app.user, 'plugin', 'index', {'plugin': plugin}) %}
|
||||
{{ widgets.table_actions(event.actions) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{{ tables.data_table_footer(plugins) }}
|
||||
{% if extensions|length > 0 %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% from "macros/widgets.html.twig" import card_tool_button %}
|
||||
{% block box_title %}{{ 'plugin.marketplace'|trans({}, 'plugins') }}{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_tools %}
|
||||
{{ card_tool_button('shop', {'title': 'shop', 'translation_domain': 'plugins', 'target': '_blank', 'url': constant('App\\Constants::HOMEPAGE') ~ '/store/', 'icon': false, 'class': 'btn-warning'}) }}
|
||||
{% endblock %}
|
||||
{% block box_body %}
|
||||
{% set columns = {
|
||||
'name': {'class': 'alwaysVisible'},
|
||||
'description': {'class': 'd-none d-md-table-cell'},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
} %}
|
||||
|
||||
{% set tableName = 'extensions' %}
|
||||
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{{ tables.datatable_header(tableName, columns, null, {columnConfig: false}) }}
|
||||
{% for extension in extensions|filter(e => e.bundle is not null and e.bundle not in installed) %}
|
||||
<tr>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
|
||||
<a href="{{ extension.url }}" title="{{ 'homepage'|trans }}" target="_blank">{{ extension.name }}</a>
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}">{{ extension.description }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
|
||||
{% if extension.buy is defined and extension.buy is not empty %}
|
||||
<a class="btn btn-icon btn-outline-secondary" href="{{ extension.buy }}" title="{{ 'buy'|trans({}, 'plugins') }}" target="_blank">{{ icon('shop') }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{{ tables.data_table_footer(plugins) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
{% macro projects(view, query) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'projects', view, {'query': query}) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro project(project, view, isTable) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'project', view, {'project': project, 'token': csrf_token('project.duplicate')}) %}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
{% set currency = project.customer.currency %}
|
||||
{% if project.isMonthlyBudget() %}
|
||||
<h4>
|
||||
{%- if showMoneyBudget %}{{ 'label.budget'|trans }}{% else %}{{ 'label.timeBudget'|trans }}{% endif -%}
|
||||
: {{ 'label.budgetType_month'|trans }}
|
||||
{%- if showMoneyBudget %}{{ 'budget'|trans }}{% else %}{{ 'timeBudget'|trans }}{% endif -%}
|
||||
: {{ 'budgetType_month'|trans }}
|
||||
</h4>
|
||||
{% set chartData = [] %}
|
||||
{% set chartLabels = [] %}
|
||||
@@ -45,7 +45,7 @@
|
||||
{{ bar_chart(chartPrefix, chartLabels, [chartData], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix}) }}
|
||||
{% else %}
|
||||
<h4>
|
||||
{%- if showMoneyBudget %}{{ 'label.budget'|trans }}{% else %}{{ 'label.timeBudget'|trans }}{% endif -%}
|
||||
{%- if showMoneyBudget %}{{ 'budget'|trans }}{% else %}{{ 'timeBudget'|trans }}{% endif -%}
|
||||
</h4>
|
||||
{% set chartData = [] %}
|
||||
{% set chartLabels = [] %}
|
||||
@@ -70,12 +70,9 @@
|
||||
'label': (showMoneyBudget ? totalBudget|money(currency) : totalBudget|duration),
|
||||
'value': (showMoneyBudget ? totalBudget|chart_money : totalBudget|chart_duration),
|
||||
} %}
|
||||
{# line chart do not support coloring negative areas #}
|
||||
{#
|
||||
{% if totalBudget < 0 %}
|
||||
{% set chartValue = chartValue|merge({'color': 'red'}) %}
|
||||
{% endif %}
|
||||
#}
|
||||
{% set chartData = chartData|merge([chartValue]) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'projects'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'delete') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% set inUse = (stats.counter > 0) %}
|
||||
|
||||
{% set params = {
|
||||
'%records%': stats.counter,
|
||||
'%duration%': stats.duration|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,
|
||||
'used': (stats.counter > 0),
|
||||
'back': path('admin_project')
|
||||
}) }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,43 +1,39 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'projects'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'project_details') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% set can_edit = is_granted('edit', project) %}
|
||||
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% embed '@theme/embeds/card.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-default btn-sm" 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>
|
||||
{{ widgets.card_tool_button('edit', {'class': 'modal-ajax-form open-edit', 'title': 'action.edit', 'url': path('admin_project_edit', {'id': project.id})}) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block box_title %}
|
||||
{{ widgets.label_name(project.name, project.color) }}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_body_class %}p-0{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if project.comment is not empty %}
|
||||
<div class="comment">
|
||||
<div class="comment p-3">
|
||||
{{ project.comment|comment2html(true) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<table class="table table-hover dataTable">
|
||||
{% if not project.visible %}
|
||||
<tr class="{{ widgets.class_project_row(project, now) }}">
|
||||
<th>{{ 'label.visible'|trans }}</th>
|
||||
<th>{{ 'visible'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ widgets.label_boolean(project.visible) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr {{ widgets.customer_row_attr(project.customer, now) }}>
|
||||
<th>{{ 'label.customer'|trans }}</th>
|
||||
<th>{{ 'customer'|trans }}</th>
|
||||
<td>
|
||||
{{ widgets.label_customer(project.customer) }}
|
||||
</td>
|
||||
@@ -51,7 +47,7 @@
|
||||
{% if is_granted('details', project) %}
|
||||
{% if not project.billable %}
|
||||
<tr>
|
||||
<th>{{ 'label.billable'|trans }}</th>
|
||||
<th>{{ 'billable'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ widgets.label_boolean(project.billable) }}
|
||||
</td>
|
||||
@@ -59,7 +55,7 @@
|
||||
{% endif %}
|
||||
{% if project.orderNumber is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.orderNumber'|trans }}</th>
|
||||
<th>{{ 'orderNumber'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ project.orderNumber }}
|
||||
</td>
|
||||
@@ -67,25 +63,25 @@
|
||||
{% endif %}
|
||||
{% if project.orderDate is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.orderDate'|trans }}</th>
|
||||
<th>{{ 'orderDate'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ project.orderDate|date_full(true) }}
|
||||
{{ project.orderDate|date_short }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if project.start is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.project_start'|trans }}</th>
|
||||
<th>{{ 'project_start'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ project.start|date_full(true) }}
|
||||
{{ project.start|date_short }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if project.end is not empty %}
|
||||
<tr>
|
||||
<th>{{ 'label.project_end'|trans }}</th>
|
||||
<th>{{ 'project_end'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ project.end|date_full(true) }}
|
||||
{{ project.end|date_short }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -93,9 +89,9 @@
|
||||
{% if project.hasBudget() and is_granted('budget', project) %}
|
||||
<tr>
|
||||
<th>
|
||||
{{ 'label.budget'|trans }}
|
||||
{{ 'budget'|trans }}
|
||||
{% if project.isMonthlyBudget() %}
|
||||
({{ 'label.budgetType_month'|trans }})
|
||||
({{ 'budgetType_month'|trans }})
|
||||
{% endif %}
|
||||
</th>
|
||||
<td colspan="3">
|
||||
@@ -106,9 +102,9 @@
|
||||
{% if project.hasTimeBudget() and is_granted('time', project) %}
|
||||
<tr>
|
||||
<th>
|
||||
{{ 'label.timeBudget'|trans }}
|
||||
{{ 'timeBudget'|trans }}
|
||||
{% if project.isMonthlyBudget() %}
|
||||
({{ 'label.budgetType_month'|trans }})
|
||||
({{ 'budgetType_month'|trans }})
|
||||
{% endif %}
|
||||
</th>
|
||||
<td colspan="3">
|
||||
@@ -118,7 +114,7 @@
|
||||
{% endif %}
|
||||
{% if not project.globalActivities %}
|
||||
<tr>
|
||||
<th>{{ 'label.globalActivities'|trans }}</th>
|
||||
<th>{{ 'globalActivities'|trans }}</th>
|
||||
<td colspan="3">
|
||||
{{ widgets.label_boolean(project.globalActivities) }}
|
||||
</td>
|
||||
@@ -136,6 +132,10 @@
|
||||
|
||||
{{ render(controller('App\\Controller\\ProjectController::activitiesAction', {'project': project.id, 'page': 1})) }}
|
||||
|
||||
{% if can_edit %}
|
||||
{{ include('embeds/rates-table.html.twig', {'id': 'project_rates_box', 'entity': project, 'create_url': path('admin_project_rate_add', {'id': project.id}), 'delete_route': 'delete_project_rate', 'currency': project.customer.currency, 'edit_route': 'admin_project_rate_edit'}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if stats is not null %}
|
||||
{{ include('embeds/budgets.html.twig', {'entity': project, 'stats': stats, 'currency': project.customer.currency}) }}
|
||||
{% endif %}
|
||||
@@ -144,10 +144,6 @@
|
||||
{{ render(controller(controller, {'project': project, 'page': 1})) }}
|
||||
{% endfor %}
|
||||
|
||||
{% if can_edit %}
|
||||
{{ include('embeds/rates-table.html.twig', {'id': 'project_rates_box', 'entity': project, 'create_url': path('admin_project_rate_add', {'id': project.id}), 'delete_route': 'delete_project_rate', 'currency': project.customer.currency}) }}
|
||||
{% endif %}
|
||||
|
||||
{% if teams is not null%}
|
||||
{% set options = {'teams': teams, 'team': team} %}
|
||||
{% if is_granted('permissions', project) %}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'projects'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'edit') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% 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': (project.id is null ? 'create-project'|trans({}, 'actions') : 'edit'|trans({}, 'actions')),
|
||||
'form': form,
|
||||
@@ -14,10 +10,10 @@
|
||||
{% embed formEditTemplate with formOptions %}
|
||||
{% block form_body %}
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<div class="col-md-9">
|
||||
{{ form_row(form.name) }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.color) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,26 +52,38 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.invoiceText is defined %}
|
||||
{{ form_row(form.invoiceText) }}
|
||||
{% if form.teams is defined %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{{ form_row(form.teams) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.visible) }}
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.billable) }}
|
||||
</div>
|
||||
<div class="col-md-4 text-right">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.globalActivities) }}
|
||||
</div>
|
||||
</div>
|
||||
{% if form.metaFields is defined and form.metaFields is not empty %}
|
||||
{% for meta in form.metaFields|sort((a, b) => a.vars.data.order <=> b.vars.data.order) %}
|
||||
{{ form_row(meta) }}
|
||||
{% endfor %}
|
||||
{{ form_row(form.metaFields) }}
|
||||
{% endif %}
|
||||
{{ form_widget(form) }}
|
||||
{% embed '@theme/embeds/card.html.twig' with {collapsed: true} %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'invoices'|trans }}{% endblock %}
|
||||
{% block box_body_class %}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if form.invoiceText is defined %}
|
||||
{{ form_row(form.invoiceText) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{{ form_rest(form) }}
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'project': project, 'activities': activities, 'page': page} %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {'project': project, 'activities': activities, 'page': page} %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'activities'|trans }}{% endblock %}
|
||||
{% block box_attributes %}
|
||||
id="activity_list_box" data-href="{{ path('project_activities', {'id': project.id}) }}" data-reload="kimai.activityUpdate kimai.activityDelete"
|
||||
id="activity_list_box" data-reload="kimai.activityUpdate kimai.activityDelete"
|
||||
{% endblock %}
|
||||
{% block box_tools %}
|
||||
{% if project.visible and project.customer.visible and is_granted('create_activity') %}
|
||||
<a class="modal-ajax-form open-edit btn btn-default btn-sm btn-pager" data-href="{{ path('admin_activity_create_with_project', {'project': project.id}) }}" data-toggle="tooltip" data-placement="top" title="{{ 'create'|trans }}"><i class="{{ 'create'|icon }}"></i></a>
|
||||
{% endif %}
|
||||
{% if activities|length > 0 %}
|
||||
{{ pagination(activities, { css_container_class: 'pagination pagination-sm inline', routeName: 'project_activities', routeParams: {'id': project.id} }) }}
|
||||
{% endif %}
|
||||
{%- if project.visible and project.customer.visible and is_granted('create_activity') -%}
|
||||
{{ widgets.card_tool_button('create', {'class': 'modal-ajax-form open-edit', 'title': 'create', 'url': path('admin_activity_create_with_project', {'project': project.id})}) }}
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}no-padding{% endblock %}
|
||||
{% block box_tools_attributes %}data-page="{{ page }}"{% endblock %}
|
||||
{% block box_footer %}
|
||||
{%- if activities|length > 0 -%}
|
||||
<div class="d-flex">
|
||||
{{ pagination(activities, { css_container_class: 'pagination ms-auto m-0', routeName: 'project_activities', routeParams: {'id': project.id} }) }}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
{% block box_body_class %}{%- if activities|length > 0 -%}p-0{%- endif -%}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if activities|length == 0 %}
|
||||
<div class="comment">{{ 'error.no_entries_found'|trans }}</div>
|
||||
@@ -22,10 +25,10 @@
|
||||
<table class="table table-hover dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.name'|trans }}</th>
|
||||
<th class="hidden-xs">{{ 'label.comment'|trans }}</th>
|
||||
<th class="w-min text-center">{{ 'label.visible'|trans }}</th>
|
||||
<th class="w-min text-center">{{ 'label.team'|trans }}</th>
|
||||
<th>{{ 'name'|trans }}</th>
|
||||
<th class="d-none d-md-table-cell">{{ 'comment'|trans }}</th>
|
||||
<th class="d-none d-sm-table-cell w-min text-center">{{ 'visible'|trans }}</th>
|
||||
<th class="d-none d-sm-table-cell w-min text-center">{{ 'team'|trans }}</th>
|
||||
<th class="actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -33,9 +36,9 @@
|
||||
{% for activity in activities %}
|
||||
<tr {{ widgets.activity_row_attr(activity, now) }}>
|
||||
<td>{{ widgets.label_activity(activity) }}</td>
|
||||
<td class="hidden-xs">{{ activity.comment|comment1line(not app.user.smallLayout) }}</td>
|
||||
<td class="w-min text-center">{{ widgets.label_visible(activity.visible) }}</td>
|
||||
<td class="w-min text-center">{{ widgets.badge_team_access(activity.teams) }}</td>
|
||||
<td class="d-none d-md-table-cell">{{ activity.comment|comment1line(false) }}</td>
|
||||
<td class="d-none d-sm-table-cell w-min text-center">{{ widgets.label_visible(activity.visible) }}</td>
|
||||
<td class="d-none d-sm-table-cell w-min text-center">{{ widgets.badge_team_access(activity.teams) }}</td>
|
||||
<td class="actions">{{ actions.activity(activity, 'project_details', true) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -45,7 +48,7 @@
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
<script type="text/javascript">
|
||||
{% set eventName = app.request.xmlHttpRequest ? 'kimai.reloadPage' : 'kimai.initialized' %}
|
||||
{% set eventName = kimai_context.javascriptRequest ? 'kimai.reloadPage' : 'kimai.initialized' %}
|
||||
document.addEventListener('{{ eventName }}', function() {
|
||||
KimaiPaginatedBoxWidget.create('#activity_list_box');
|
||||
});
|
||||
|
||||
@@ -1,93 +1,45 @@
|
||||
{% 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 "project/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'name': {'class': 'alwaysVisible'},
|
||||
'customer': {'class': 'hidden-xs'},
|
||||
'comment': {'class': 'hidden-xs hidden-sm', 'title': 'label.description'|trans},
|
||||
'orderNumber': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'orderDate': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'project_start': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
'project_end': {'class': 'hidden-xs hidden-sm hidden'},
|
||||
} %}
|
||||
{% for field in metaColumns %}
|
||||
{% set columns = columns|merge({
|
||||
('mf_' ~ field.name): {'title': field.label|trans, 'class': 'hidden-xs hidden-sm mf_' ~ field.name, 'orderBy': false}
|
||||
}) %}
|
||||
{% endfor %}
|
||||
{% if is_granted('budget_money', 'project') %}
|
||||
{% set columns = columns|merge({
|
||||
'budget': {'class': 'hidden-xs hidden-sm hidden text-right w-min', 'title': 'label.budget'|trans},
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('budget_time', 'project') %}
|
||||
{% set columns = columns|merge({
|
||||
'timeBudget': {'class': 'hidden-xs hidden-sm hidden text-right w-min', 'title': 'label.timeBudget'|trans},
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% set columns = columns|merge({
|
||||
'team': {'class': 'text-center w-min', 'orderBy': false},
|
||||
'visible': {'class': 'text-center hidden w-min'},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
}) %}
|
||||
{% block datatable_row_attr %} {{ widgets.project_row_attr(entry, now) }}{% endblock %}
|
||||
|
||||
{% set tableName = 'project_admin' %}
|
||||
|
||||
{% block page_title %}{{ 'projects'|trans }}{% endblock %}
|
||||
{% block page_search %}{{ search.searchModal(toolbarForm) }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.projects('index', query) }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ tables.data_table_column_modal(tableName, columns) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.nothing_found('kimai.projectUpdate kimai.projectTeamUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.projectUpdate kimai.projectDelete kimai.projectTeamUpdate'}) }}
|
||||
|
||||
{% for entry in entries %}
|
||||
<tr {{ widgets.project_row_attr(entry, now) }}>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
|
||||
{{ widgets.label_name(entry.name, entry.color) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
|
||||
<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 null %}{{ entry.orderDate|date_full(true) }}{% endif %}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_start') }}">{% if entry.start is not null %}{{ entry.start|date_full(true) }}{% endif %}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_end') }}">{% if entry.end is not null %}{{ entry.end|date_full(true) }}{% endif %}</td>
|
||||
{% for field in metaColumns %}
|
||||
<td class="{{ 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, 'budget') }}">
|
||||
{% if entry.hasBudget() and is_granted('budget', entry) %}
|
||||
{{ entry.budget|money(entry.customer.currency) }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'timeBudget') }}">
|
||||
{% if entry.hasTimeBudget() and is_granted('time', entry) %}
|
||||
{{ entry.timeBudget|duration }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
</td>
|
||||
<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, 'visible') }}">{{ widgets.label_visible(entry.visible) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">{{ actions.project(entry, 'index') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{{ tables.data_table_footer(entries, 'admin_project_paginated') }}
|
||||
{% block datatable_column_value %}
|
||||
{% if column == 'name' %}
|
||||
{{ widgets.label_name(entry.name, entry.color|colorize(entry.name)) }}
|
||||
{% elseif column == 'customer' %}
|
||||
{{ widgets.label_customer(entry.customer) }}
|
||||
{% elseif column == 'comment' %}
|
||||
{{ entry.comment|comment1line }}
|
||||
{% elseif column == 'orderNumber' %}
|
||||
{{ entry.orderNumber }}
|
||||
{% elseif column == 'orderDate' %}
|
||||
{% if entry.orderDate is not null %}{{ entry.orderDate|date_short }}{% endif %}
|
||||
{% elseif column == 'project_start' %}
|
||||
{% if entry.start is not null %}{{ entry.start|date_short }}{% endif %}
|
||||
{% elseif column == 'project_end' %}
|
||||
{% if entry.end is not null %}{{ entry.end|date_short }}{% endif %}
|
||||
{% elseif column == 'budget' %}
|
||||
{% if entry.hasBudget() and is_granted('budget', entry) %}
|
||||
{{ entry.budget|money(entry.customer.currency) }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{% elseif column == 'timeBudget' %}
|
||||
{% if entry.hasTimeBudget() and is_granted('time', entry) %}
|
||||
{{ entry.timeBudget|duration }}
|
||||
{% else %}
|
||||
–
|
||||
{% endif %}
|
||||
{% elseif column == 'billable' %}
|
||||
{{ widgets.label_boolean(entry.billable) }}
|
||||
{% elseif column == 'team' %}
|
||||
{{ widgets.badge_team_access(entry.teams) }}
|
||||
{% elseif column == 'visible' %}
|
||||
{{ widgets.label_visible(entry.visible) }}
|
||||
{% elseif column == 'actions' %}
|
||||
{{ actions.project(entry, 'index') }}
|
||||
{% elseif column starts with 'mf_' %}
|
||||
{{ widgets.meta_field_value(entry, data) }}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'projects'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'permissions') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block main %}
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': ('permissions'|trans({}, 'actions')) ~ ': ' ~ project.name,
|
||||
'form': form,
|
||||
'back': path('project_details', {'id': project.id})
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
{% import "project/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'projects'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.project(project, 'rates') }}{% endblock %}
|
||||
{% extends kimai_context.modalRequest ? 'form.html.twig' : 'base.html.twig' %}
|
||||
|
||||
{% block main %}
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
{{ include(kimai_context.modalRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': project.name,
|
||||
'form': form,
|
||||
'back': path('admin_project')
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{% macro quickEntries(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set event = actions(app.user, 'weekly_times', view, {}) %}
|
||||
{{ widgets.page_actions(event.actions) }}
|
||||
{% endmacro %}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user