Edit file File name : index.html.twig Content :{% 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 columns = { '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}, } %} {% set tableName = 'export' %} {% set editExported = is_granted('edit_exported_timesheet') %} {% 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) }} {% endblock %} {% block main_before %} {{ tables.data_table_column_modal(tableName, columns) }} {% endblock %} {% block main %} {% embed '@AdminLTE/Widgets/box-widget.html.twig' %} {% import "macros/search.html.twig" as search %} {% form_theme form '@AdminLTE/layout/form-theme-horizontal.html.twig' %} {% block box_title %}{{ 'export.filter'|trans }}{% endblock %} {% block box_before %}{{ form_start(form) }}{% endblock %} {% block box_body %} {{ form_errors(form) }} {{ form_row(form.searchTerm) }} {{ form_row(form.daterange) }} {{ form_row(form.customers) }} {{ form_row(form.projects) }} {{ form_row(form.activities) }} {{ form_row(form.tags) }} {% if form.users is defined %} {{ form_row(form.users) }} {% endif %} {{ form_row(form.billable) }} {{ form_row(form.exported) }} {{ form_row(form.state) }} {{ form_row(form.markAsExported) }} {% endblock %} {% block box_footer%} {{ search.searchButton(form) }} {% endblock %} {% block box_after %}{{ form_end(form) }}{% endblock %} {% endembed %} {% if too_many is same as (true) %} {{ widgets.callout('danger', 'error.too_many_entries') }} {% elseif preview_show %} {% if entries is empty %} {{ widgets.nothing_found() }} {% else %} {% embed '@AdminLTE/Widgets/box-widget.html.twig' %} {% import "macros/widgets.html.twig" as widgets %} {% block box_title %} {{ 'button.preview'|trans }}: {{ 'export.title'|trans }} {% endblock %} {% block box_body_class %}no-padding{% endblock %} {% block box_footer %} {% set buttons = {} %} {% for button in renderer %} {% set title = button.title %} {% set group = [] %} {% if buttons[(title)] is defined %} {% set group = buttons[(title)] %} {% endif %} {% set group = group|merge([button]) %} {% set buttons = buttons|merge({(title): group}) %} {% endfor %} <div class="btn-group" id="export-buttons" role="group"> {% for group in buttons %} {% set button = group.0 %} {% 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 %} {% 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 %} </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> </tr> </thead> <tbody> {% for row in by_customer %} {% set currency = row.customer.currency %} <tr> <td> {{ widgets.label_customer(row.customer) }} </td> <td class="w-min text-right hidden-xs"> {{ row.duration|duration(decimal) }} </td> {# <td class="w-min text-right hidden-xs"> {{ row.internalRate|money(currency) }} </td> #} <td class="w-min text-right"> {{ row.rate|money(currency) }} </td> </tr> {% endfor %} </tbody> </table> {% endblock %} {% endembed %} {% set itemsAmount = entries|length %} {% if preview_limit %} {% set entries = entries|slice(0, preview_limit) %} {% endif %} {{ tables.datatable_header(tableName, columns, query, {}) }} {% for entry in entries %} {% set currency = entry.project.customer.currency %} {% if entry.fixedRate is not null %} {% set rate = entry.fixedRate %} {% else %} {% 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, 'user') }}"> {{ widgets.label_user(entry.user) }} </td> <td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}"> {{ widgets.label_project(entry.project) }} <br> <small>{{ widgets.label_customer(entry.project.customer) }}</small> </td> <td class="{{ tables.data_table_column_class(tableName, columns, 'activity') }}"> {% if entry.activity is not null %} {{ widgets.label_activity(entry.activity) }} {% endif %} </td> <td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}"> {{ entry.description|desc2html }} </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, 'duration') }}" data-duration="{{ entry.duration }}"> {{ entry.duration|duration(decimal) }} </td> <td class="{{ tables.data_table_column_class(tableName, columns, 'rate_internal') }}"> {{ 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') }}"> {% 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> {% else %} {{ 'entryState.exported'|trans }} {% endif %} {% else %} <button type="button" class="btn btn-default exportBtn" data-toggle="button" aria-pressed="false" autocomplete="off" data-exported-text="{{ 'entryState.exported'|trans }}" data-clean-text="{{ 'entryState.not_exported'|trans }}" data-timesheet="{{ entry.id }}"> {{ 'entryState.not_exported'|trans }} </button> {% endif %} {% else %} {% if entry.exported %} {{ 'entryState.exported'|trans }} {% else %} {{ 'entryState.not_exported'|trans }} {% endif %} {% endif %} </td> </tr> {% endfor %} {% if preview_limit and itemsAmount > preview_limit %} <tr class="warning"> <td colspan="10">» {{ 'preview.skipped_rows'|trans({'%rows%': (itemsAmount - preview_limit)}) }}</td> </tr> {% endif %} {{ tables.data_table_footer(entries) }} {% endif %} {% endif %} {% endblock %} {% block javascripts %} {{ parent() }} <script type="text/javascript"> function confirmToggleState(button) { var ALERT = kimai.getPlugin('alert'); var message = '{{ 'export.clear_all'|trans }}'; var hasActive = true; 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'); } } }); } 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')); }); jQuery('#export-toggle-button').on('click', function () { confirmToggleState(this); }); 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); }); }); </script> {% endblock %} Save