link reporting screen for users and month charts (#1842)
This commit is contained in:
@@ -20,6 +20,7 @@ use App\Form\UserRolesType;
|
||||
use App\Form\UserTeamsType;
|
||||
use App\Repository\TeamRepository;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Utils\LocaleSettings;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
@@ -69,7 +70,7 @@ class ProfileController extends AbstractController
|
||||
* @Route(path="/{username}", name="user_profile", methods={"GET"})
|
||||
* @Security("is_granted('view', profile)")
|
||||
*/
|
||||
public function indexAction(User $profile, TimesheetRepository $repository)
|
||||
public function indexAction(User $profile, TimesheetRepository $repository, LocaleSettings $localeSettings)
|
||||
{
|
||||
$userStats = $repository->getUserStatistics($profile);
|
||||
$monthlyStats = $repository->getMonthlyStats($profile);
|
||||
@@ -79,6 +80,7 @@ class ProfileController extends AbstractController
|
||||
'user' => $profile,
|
||||
'stats' => $userStats,
|
||||
'years' => $monthlyStats,
|
||||
'stat_date_format' => $localeSettings->getDatePickerFormat(),
|
||||
];
|
||||
|
||||
return $this->render('user/stats.html.twig', $viewVars);
|
||||
|
||||
@@ -64,21 +64,23 @@ final class ReportingController extends AbstractController
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
|
||||
$form = $this->createForm(MonthByUserForm::class, $values, [
|
||||
'method' => 'POST',
|
||||
'include_user' => $this->isGranted('view_other_timesheet') && $user->hasTeamAssignment(),
|
||||
]);
|
||||
|
||||
$form->handleRequest($request);
|
||||
$form->submit($request->query->all(), false);
|
||||
|
||||
if ($form->isSubmitted() && !$form->isValid()) {
|
||||
if ($values->getUser() === null) {
|
||||
$values->setUser($user);
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
}
|
||||
|
||||
if ($user !== $values->getUser() && !$this->isGranted('view_other_timesheet')) {
|
||||
throw new AccessDeniedException('User is not allowed to see other users timesheet');
|
||||
}
|
||||
|
||||
if ($values->getDate() === null) {
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
}
|
||||
|
||||
$start = $values->getDate();
|
||||
$start->modify('first day of 00:00:00');
|
||||
|
||||
@@ -124,16 +126,18 @@ final class ReportingController extends AbstractController
|
||||
$values = new MonthlyUserList();
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
|
||||
$form = $this->createForm(MonthlyUserListForm::class, $values, [
|
||||
'method' => 'POST',
|
||||
]);
|
||||
$form = $this->createForm(MonthlyUserListForm::class, $values, []);
|
||||
|
||||
$form->handleRequest($request);
|
||||
$form->submit($request->query->all(), false);
|
||||
|
||||
if ($form->isSubmitted() && !$form->isValid()) {
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
}
|
||||
|
||||
if ($values->getDate() === null) {
|
||||
$values->setDate($this->dateTimeFactory->getStartOfMonth());
|
||||
}
|
||||
|
||||
$start = $values->getDate();
|
||||
$start->modify('first day of 00:00:00');
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to select a month via picker and select previous and next month.
|
||||
*
|
||||
* Always falls back to the current month if none or an invalid date is given.
|
||||
*/
|
||||
final class MonthPickerType extends AbstractType
|
||||
{
|
||||
@@ -67,6 +69,7 @@ final class MonthPickerType extends AbstractType
|
||||
$date = $this->dateTime->getStartOfMonth();
|
||||
}
|
||||
|
||||
$view->vars['month'] = $date;
|
||||
$view->vars['previousMonth'] = (clone $date)->modify('-1 month');
|
||||
$view->vars['nextMonth'] = (clone $date)->modify('+1 month');
|
||||
$view->vars['momentFormat'] = (new MomentFormatConverter())->convert($this->localeSettings->getDateTypeFormat());
|
||||
|
||||
@@ -17,6 +17,16 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class MonthByUserForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* Simplify cross linking between pages by removing the block prefix.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -37,6 +47,8 @@ class MonthByUserForm extends AbstractType
|
||||
$resolver->setDefaults([
|
||||
'data_class' => MonthByUser::class,
|
||||
'include_user' => false,
|
||||
'csrf_protection' => false,
|
||||
'method' => 'GET',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,16 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class MonthlyUserListForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* Simplify cross linking between pages by removing the block prefix.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -31,6 +41,8 @@ class MonthlyUserListForm extends AbstractType
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => MonthlyUserList::class,
|
||||
'csrf_protection' => false,
|
||||
'method' => 'GET',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,13 +137,18 @@ final class LocaleHelper
|
||||
if (null === $this->moneyFormatterNoCurrency) {
|
||||
// if anyone knows a better way of achieving this, please let me know!
|
||||
$this->moneyFormatterNoCurrency = new NumberFormatter($this->locale, NumberFormatter::CURRENCY);
|
||||
$pattern = $this->moneyFormatterNoCurrency->getPattern();
|
||||
$pattern = str_replace('¤ ', '¤', $pattern);
|
||||
$pattern = str_replace(' ¤', '¤', $pattern);
|
||||
$this->moneyFormatterNoCurrency->setPattern($pattern);
|
||||
$this->moneyFormatterNoCurrency->setSymbol(NumberFormatter::CURRENCY_SYMBOL, '');
|
||||
$this->moneyFormatterNoCurrency->setSymbol(NumberFormatter::CURRENCY_CODE, '');
|
||||
|
||||
$this->moneyFormatterNoCurrency->setTextAttribute(NumberFormatter::CURRENCY_CODE, '');
|
||||
$this->moneyFormatterNoCurrency->setSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL, '');
|
||||
$this->moneyFormatterNoCurrency->setSymbol(NumberFormatter::CURRENCY_SYMBOL, '');
|
||||
|
||||
// don't understand why this is needed, I'd say this shouldn't be necessary after the above calls
|
||||
// even worse: the logic changes either between PHP/ICU versions
|
||||
$pattern = $this->moneyFormatterNoCurrency->getPattern();
|
||||
$pattern = str_replace(['¤ ', ' ¤', '-¤', ' XXX', 'XXX '], '¤', $pattern);
|
||||
$pattern = str_replace('XXX', '¤', $pattern);
|
||||
$pattern = str_replace('¤', '', $pattern);
|
||||
$this->moneyFormatterNoCurrency->setPattern($pattern);
|
||||
}
|
||||
|
||||
return $this->moneyFormatterNoCurrency;
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<i class="{{ 'left'|icon }}"></i>
|
||||
</a>
|
||||
<a class="btn btn-default" href="#" onclick="return false;">
|
||||
<span id="{{ form.vars.id }}_month_name">{{ data|month_name|trans ~ ' ' ~ data|date_format('Y') }}</span>
|
||||
<span id="{{ form.vars.id }}_month_name">{{ month|month_name|trans ~ ' ' ~ month|date_format('Y') }}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn-right" href="#" onclick="$('#{{ form.vars.id }}').val('{{nextMonth|date_short}}').change()">
|
||||
<i class="{{ 'right'|icon }}"></i>
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if user.id is not empty %}
|
||||
{% set view_other = is_granted('view_other_timesheet') %}
|
||||
{% if is_granted('view', user) %}
|
||||
{% set actions = actions|merge({'profile-stats': {'url': path('user_profile', {'username' : user.username})}}) %}
|
||||
{% endif %}
|
||||
@@ -70,7 +71,12 @@
|
||||
{% if actions|length > 0 %}
|
||||
{% set actions = actions|merge({'divider': null}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_other_timesheet') and user.enabled %}
|
||||
{% if is_granted('view_reporting') %}
|
||||
{% if view_other or app.user.id == user.id %}
|
||||
{% set actions = actions|merge({'report': path('report_user_month', {'user': user.id})}) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if view_other and user.enabled %}
|
||||
{% set actions = actions|merge({'timesheet': path('admin_timesheet', {'users': [user.id]})}) %}
|
||||
{% endif %}
|
||||
{% if view == 'index' and is_granted('delete', user) %}
|
||||
|
||||
@@ -49,6 +49,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
{%- if is_granted('view_reporting') and (app.user.id == user.id or is_granted('view_other_timesheet')) -%}
|
||||
,
|
||||
onClick: function(event, elements) {
|
||||
var element = elements[0];
|
||||
var month = this.data.datasets[0].monthData[element._index];
|
||||
var formattedMonth = moment(month).format('{{ stat_date_format }}');
|
||||
var reportUrl = '{{ path('report_user_month', {'user': user.id, 'date': 'XXXXX'})|raw }}'.replace('XXXXX', formattedMonth);
|
||||
document.location = reportUrl;
|
||||
}
|
||||
{% endif %}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
@@ -58,7 +68,6 @@
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||
{% block box_title %}{{ year }}{% endblock %}
|
||||
{% block box_body %}
|
||||
|
||||
<div class="chart">
|
||||
<canvas id="userProfileChart{{ year }}" style="height: {{ kimai_context.chart.height }}px;"></canvas>
|
||||
</div>
|
||||
@@ -77,20 +86,24 @@
|
||||
{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
realData : [
|
||||
realData: [
|
||||
{% for month in yearStat.months %}
|
||||
'{{ month.totalDuration|duration }}'
|
||||
{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
],
|
||||
monthData: [
|
||||
{% for month in yearStat.months %}
|
||||
'{{ yearStat.year }}-{{ month.month|length == 1 ? '0'~ month.month : month.month }}-01'
|
||||
{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var userProfileChartCanvas{{ year }} = $("#userProfileChart{{ year }}").get(0).getContext("2d");
|
||||
|
||||
var userProfileChart{{ year }} = new Chart(
|
||||
userProfileChartCanvas{{ year }}, {
|
||||
$("#userProfileChart{{ year }}").get(0).getContext("2d"), {
|
||||
type: 'bar',
|
||||
data: userProfileChartData{{ year }},
|
||||
options: userProfileChartOptions()
|
||||
|
||||
Reference in New Issue
Block a user