support multiple teamleads in each team (#2702)
* fix jumping avatars * fix line-break after color dot for long names
This commit is contained in:
@@ -23,13 +23,13 @@
|
||||
{% for type, messages in app.session.flashbag.all %}
|
||||
{% for message in messages %}
|
||||
{% if type == 'error' %}
|
||||
ALERT.error('{{ message|trans({}, domain) }}');
|
||||
ALERT.error('{{ message|trans({}, domain)|e('js') }}');
|
||||
{% elseif type == 'warning' %}
|
||||
ALERT.warning('{{ message|trans({}, domain) }}');
|
||||
ALERT.warning('{{ message|trans({}, domain)|e('js') }}');
|
||||
{% elseif type == 'success' %}
|
||||
ALERT.success('{{ message|trans({}, domain) }}');
|
||||
ALERT.success('{{ message|trans({}, domain)|e('js') }}');
|
||||
{% else %}
|
||||
ALERT.info('{{ message|trans({}, domain) }}');
|
||||
ALERT.info('{{ message|trans({}, domain)|e('js') }}');
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
@@ -123,7 +123,7 @@
|
||||
{% 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': create_date('now'), 'activity': {'name': ''}, 'project': {'name': '', 'customer': {'name': ''}}}] %}
|
||||
{% 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 %}">
|
||||
@@ -132,7 +132,7 @@
|
||||
<div class="ddt-small ticktac-single ticktac-running">
|
||||
<div class="ticktac-stop">
|
||||
<a 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|date_format(constant('DATE_ISO8601')) }}">{{ entry|duration }}</span>
|
||||
<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>
|
||||
@@ -156,7 +156,7 @@
|
||||
<h4>
|
||||
<span data-replacer="activity">{{ entry.activity.name }}</span>
|
||||
<small>
|
||||
<span data-replacer="duration" data-title="true" data-since="{{ entry.begin|date_format(constant('DATE_ISO8601')) }}">{{ entry|duration }}</span>
|
||||
<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>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{% if form_theme is defined %}
|
||||
{% form_theme form form_theme %}
|
||||
{% 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">
|
||||
{% block form_before %}{% endblock %}
|
||||
{{ form_start(form) }}
|
||||
@@ -19,10 +21,10 @@
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
|
||||
{% if back|default(false) %}
|
||||
<a href="{{ back }}" class="btn btn-link">{{ 'action.back'|trans }}</a>
|
||||
{% if _back is not same as (false) %}
|
||||
<a href="{{ _back }}" class="btn btn-link">{{ 'action.back'|trans }}</a>
|
||||
{% endif %}
|
||||
{% if reset|default(true) %}
|
||||
{% if _reset is not same as (false) %}
|
||||
<input type="reset" value="{{ 'action.reset'|trans }}" class="btn btn-link pull-right" />
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,31 @@
|
||||
{{ parent() }}
|
||||
{% endblock form_label %}
|
||||
|
||||
{% 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) }}
|
||||
</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">
|
||||
|
||||
@@ -154,16 +154,20 @@
|
||||
|
||||
{# for @internal use only #}
|
||||
{% macro label_color_dot(type, isVisible, name, url, color) %}
|
||||
{% apply spaceless %}
|
||||
<span class="label-{{ type }}{{ isVisible ? '' : ' 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>
|
||||
</span>
|
||||
{% endapply %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro color_dot(color, tooltip) %}
|
||||
@@ -511,23 +515,25 @@
|
||||
{% endif %}
|
||||
<tbody>
|
||||
{% for team in teams|sort((a, b) => a.name <=> b.name) %}
|
||||
{% set users = team.users|filter(u => u.enabled) %}
|
||||
{% set userTeamCount = users|length %}
|
||||
{% 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>
|
||||
{{ _self.label_team(team) }}
|
||||
{{ _self.label_color_dot('color', true, team.name, null, team.color|colorize(team.name)) }}
|
||||
</td>
|
||||
<td class="avatars">
|
||||
{{ _self.user_avatar(team.teamlead, ('label.teamlead'|trans ~ ': ' ~ team.teamlead.displayName), 'teamlead') }}
|
||||
{% set teamHiddenId = 'team_' ~ team.id ~ '_hiddenUser' ~ random() %}
|
||||
{% set counter = 0 %}
|
||||
{% for user in users %}
|
||||
{% if user != team.teamlead %}
|
||||
{% for member in members|sort((a, b) => a.teamlead < b.teamlead) %}
|
||||
{% set user = member.user %}
|
||||
{% if member.teamlead %}
|
||||
{{ _self.user_avatar(user, ('label.teamlead'|trans ~ ': ' ~ user.displayName), 'teamlead') }}
|
||||
{% else %}
|
||||
{{ _self.user_avatar(user) }}
|
||||
{% set counter = counter + 1 %}
|
||||
{% endif %}
|
||||
{% set counter = counter + 1 %}
|
||||
{% if userTeamCount > (collapseAt + 1) and counter == (collapseAt - 1) and loop.index != userTeamCount %}
|
||||
<a href="#" onclick="$('#{{ teamHiddenId }}').toggleClass('hidden');$(this).hide();return false;" class="badge">{{ 'label.plus_more'|trans({'%count%': (userTeamCount - collapseAt)}) }}</a>
|
||||
<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 %}
|
||||
|
||||
@@ -8,25 +8,21 @@
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{{ include('default/_form.html.twig', {
|
||||
'title': team.name|default('create'|trans),
|
||||
'form': form,
|
||||
'back': path('admin_team')
|
||||
}) }}
|
||||
{% include 'team/member-form.html.twig' %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{% if not customerForm is null %}
|
||||
{{ include('default/_form.html.twig', {
|
||||
'title': 'teams.customer_access'|trans({}, 'teams'),
|
||||
'form': customerForm,
|
||||
}) }}
|
||||
{{ include('default/_form.html.twig', {
|
||||
'title': 'teams.customer_access'|trans({}, 'teams'),
|
||||
'form': customerForm,
|
||||
}) }}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if not projectForm is null %}
|
||||
{{ include('default/_form.html.twig', {
|
||||
'title': 'teams.project_access'|trans({}, 'teams'),
|
||||
'form': projectForm,
|
||||
}) }}
|
||||
{{ include('default/_form.html.twig', {
|
||||
'title': 'teams.project_access'|trans({}, 'teams'),
|
||||
'form': projectForm,
|
||||
}) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,9 +6,5 @@
|
||||
{% block page_actions %}{{ actions.team(team, 'edit') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
|
||||
'title': team.name|default('create'|trans),
|
||||
'form': form,
|
||||
'back': path('admin_team')
|
||||
}) }}
|
||||
{% include 'team/member-form.html.twig' %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
{% set columns = {
|
||||
'name': {'class': ''},
|
||||
'teamlead': {'class': ''},
|
||||
'teamlead': {'class': '', 'orderBy': false},
|
||||
'user': {'class': 'hidden-xs', 'orderBy': false},
|
||||
'actions': {'class': 'actions alwaysVisible'},
|
||||
} %}
|
||||
@@ -30,11 +30,17 @@
|
||||
{{ widgets.label_name(team.name, team.color) }}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'teamlead') }}">
|
||||
{{ widgets.label_user(team.teamlead) }}
|
||||
{% for member in team.members %}
|
||||
{% if member.teamlead %}
|
||||
{{ widgets.label_user(member.user) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">
|
||||
{% for user in team.users %}
|
||||
{{ widgets.label_user(user) }}
|
||||
{% for member in team.members %}
|
||||
{% if not member.teamlead %}
|
||||
{{ widgets.label_user(member.user) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
|
||||
|
||||
44
templates/team/member-form.html.twig
Normal file
44
templates/team/member-form.html.twig
Normal file
@@ -0,0 +1,44 @@
|
||||
{% set formEditTemplate = app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
||||
{% set formOptions = {
|
||||
'title': team.name|default('create'|trans),
|
||||
'form': form,
|
||||
'back': path('admin_team'),
|
||||
'reset': false
|
||||
} %}
|
||||
{% embed formEditTemplate with formOptions %}
|
||||
{% block form_body %}
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{{ form_row(form.name) }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.color) }}
|
||||
</div>
|
||||
</div>
|
||||
{{ form_row(form.users) }}
|
||||
{{ form_row(form.members) }}
|
||||
{{ form_widget(form) }}
|
||||
{% endblock %}
|
||||
{% block form_after %}
|
||||
{% set blockPrefix = form.vars.id %}
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function () {
|
||||
jQuery('#{{ blockPrefix }}_users').change(function (e) {
|
||||
var select = jQuery(e.target);
|
||||
var selected = select.find(':selected');
|
||||
var username = selected.text();
|
||||
var userid = select.val();
|
||||
selected.remove();
|
||||
|
||||
var prototype = jQuery('#{{ blockPrefix }}_members');
|
||||
var counter = prototype.data('widget-counter') || prototype.children().length;
|
||||
var newWidget = prototype.attr('data-prototype');
|
||||
newWidget = newWidget.replace(/__name__/g, counter);
|
||||
newWidget = newWidget.replace(/__USERNAME__/g, username);
|
||||
prototype.data('widget-counter', ++counter);
|
||||
jQuery(newWidget).prependTo(prototype).find('input[type=hidden]').val(userid);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
@@ -48,7 +48,7 @@
|
||||
{% endembed %}
|
||||
{% endfor %}
|
||||
|
||||
{% if user.teams is not empty and (is_granted('teams', user) or (app.user == user and is_granted('view_team_member'))) %}
|
||||
{% if user.teams is not empty and (is_granted('teams', user) or is_granted('view_team_member', user)) %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'label.my_teams'|trans }}{% endblock %}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
{% extends 'user/layout.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% block profile_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="box box-{{ admin_lte_context.widget.type }} data_table">
|
||||
<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 table-hover dataTable" role="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'label.team'|trans }}</th>
|
||||
<th>{{ 'label.teamlead'|trans }}</th>
|
||||
<th>{{ 'label.user'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for team in user.teams %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ team.name }}
|
||||
</td>
|
||||
<td>
|
||||
{{ widgets.user_avatar(team.teamlead) }}
|
||||
{#
|
||||
{{ widgets.label(team.teamlead.displayName, 'success', 'label.teamlead'|trans) }}
|
||||
#}
|
||||
</td>
|
||||
<td class="avatars">
|
||||
{% for user in team.users %}
|
||||
{{ widgets.user_avatar(user) }}
|
||||
{#
|
||||
{{ widgets.label_user(user) }}
|
||||
#}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user