diff --git a/src/Controller/ProfileController.php b/src/Controller/ProfileController.php index 5540e725..e6f35431 100644 --- a/src/Controller/ProfileController.php +++ b/src/Controller/ProfileController.php @@ -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); diff --git a/src/Controller/ReportingController.php b/src/Controller/ReportingController.php index e0497906..4a80e779 100644 --- a/src/Controller/ReportingController.php +++ b/src/Controller/ReportingController.php @@ -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'); diff --git a/src/Form/Type/MonthPickerType.php b/src/Form/Type/MonthPickerType.php index 5093e9fa..35b3a21a 100644 --- a/src/Form/Type/MonthPickerType.php +++ b/src/Form/Type/MonthPickerType.php @@ -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()); diff --git a/src/Reporting/MonthByUserForm.php b/src/Reporting/MonthByUserForm.php index 65f89255..d64f3b50 100644 --- a/src/Reporting/MonthByUserForm.php +++ b/src/Reporting/MonthByUserForm.php @@ -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', ]); } } diff --git a/src/Reporting/MonthlyUserListForm.php b/src/Reporting/MonthlyUserListForm.php index 8d769864..7fffeae1 100644 --- a/src/Reporting/MonthlyUserListForm.php +++ b/src/Reporting/MonthlyUserListForm.php @@ -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', ]); } } diff --git a/src/Utils/LocaleHelper.php b/src/Utils/LocaleHelper.php index dc4bbb1a..eb830349 100644 --- a/src/Utils/LocaleHelper.php +++ b/src/Utils/LocaleHelper.php @@ -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; diff --git a/templates/form/kimai-theme.html.twig b/templates/form/kimai-theme.html.twig index 35a49b8a..30eaffc5 100644 --- a/templates/form/kimai-theme.html.twig +++ b/templates/form/kimai-theme.html.twig @@ -52,7 +52,7 @@ - {{ data|month_name|trans ~ ' ' ~ data|date_format('Y') }} + {{ month|month_name|trans ~ ' ' ~ month|date_format('Y') }} diff --git a/templates/user/actions.html.twig b/templates/user/actions.html.twig index 38e594a7..016b64ca 100644 --- a/templates/user/actions.html.twig +++ b/templates/user/actions.html.twig @@ -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) %} diff --git a/templates/user/stats.html.twig b/templates/user/stats.html.twig index d9a611f9..ef66d382 100644 --- a/templates/user/stats.html.twig +++ b/templates/user/stats.html.twig @@ -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 %} }; } @@ -58,7 +68,6 @@ {% embed '@AdminLTE/Widgets/box-widget.html.twig' %} {% block box_title %}{{ year }}{% endblock %} {% block box_body %} -
@@ -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()