Next major version 2 with PHP 8.1, Symfony 6, Tabler UI, 2FA ... (#2902)
This commit is contained in:
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 %}
|
||||
Reference in New Issue
Block a user