added pagination to working time widget (#1418)
This commit is contained in:
@@ -179,11 +179,10 @@ kimai:
|
|||||||
# --------------------------------------------------------------------------------
|
# --------------------------------------------------------------------------------
|
||||||
dashboard:
|
dashboard:
|
||||||
user_duration:
|
user_duration:
|
||||||
title: stats.yourWorkingHours
|
title: ~
|
||||||
order: 10
|
order: 10
|
||||||
permission: view_own_timesheet
|
permission: view_own_timesheet
|
||||||
type: '\App\Widget\Type\CompoundChart'
|
widgets: [PaginatedWorkingTimeChart]
|
||||||
widgets: [DailyWorkingTimeChart, userDurationToday, userDurationWeek, userDurationMonth, userDurationYear]
|
|
||||||
user_teams:
|
user_teams:
|
||||||
title: ~
|
title: ~
|
||||||
order: 15
|
order: 15
|
||||||
|
|||||||
35
src/Controller/WidgetController.php
Normal file
35
src/Controller/WidgetController.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route(path="/widgets")
|
||||||
|
* @Security("is_granted('IS_AUTHENTICATED_REMEMBERED')")
|
||||||
|
*/
|
||||||
|
final class WidgetController extends AbstractController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route(path="/working-time/{year}/{week}", requirements={"year": "[1-9]\d*", "week": "[0-9]\d*"}, name="widgets_working_time_chart", methods={"GET"})
|
||||||
|
* @Security("is_granted('view_own_timesheet')")
|
||||||
|
*/
|
||||||
|
public function workingtimechartAction($year, $week, Request $request): Response
|
||||||
|
{
|
||||||
|
return $this->render('widget/paginatedworkingtimechart.html.twig', [
|
||||||
|
'user' => $this->getUser(),
|
||||||
|
'year' => $year,
|
||||||
|
'week' => $week,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -431,20 +431,20 @@ class TimesheetRepository extends EntityRepository
|
|||||||
*/
|
*/
|
||||||
public function getDailyStats(User $user, DateTime $begin, DateTime $end): array
|
public function getDailyStats(User $user, DateTime $begin, DateTime $end): array
|
||||||
{
|
{
|
||||||
$results = $this->getDailyData($begin, $end, $user);
|
|
||||||
|
|
||||||
/** @var Day[] $days */
|
/** @var Day[] $days */
|
||||||
$days = [];
|
$days = [];
|
||||||
|
|
||||||
// prefill the array
|
// prefill the array
|
||||||
$tmp = clone $end;
|
$tmp = clone $end;
|
||||||
$until = (int) $begin->format('Ymd');
|
$until = (int) $begin->format('Ymd');
|
||||||
while ((int) $tmp->format('Ymd') > $until) {
|
while ((int) $tmp->format('Ymd') >= $until) {
|
||||||
$tmp->modify('-1 day');
|
|
||||||
$last = clone $tmp;
|
$last = clone $tmp;
|
||||||
$days[$last->format('Ymd')] = new Day($last, 0, 0.00);
|
$days[$last->format('Ymd')] = new Day($last, 0, 0.00);
|
||||||
|
$tmp->modify('-1 day');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$results = $this->getDailyData($begin, $end, $user);
|
||||||
|
|
||||||
foreach ($results as $statRow) {
|
foreach ($results as $statRow) {
|
||||||
$dateTime = new DateTime();
|
$dateTime = new DateTime();
|
||||||
$dateTime->setDate($statRow['year'], $statRow['month'], $statRow['day']);
|
$dateTime->setDate($statRow['year'], $statRow['month'], $statRow['day']);
|
||||||
|
|||||||
@@ -63,8 +63,17 @@ class DailyWorkingTimeChart extends SimpleWidget
|
|||||||
$options = $this->getOptions($options);
|
$options = $this->getOptions($options);
|
||||||
|
|
||||||
$user = $options['user'];
|
$user = $options['user'];
|
||||||
|
if ($options['begin'] instanceof DateTime) {
|
||||||
|
$begin = $options['begin'];
|
||||||
|
} else {
|
||||||
$begin = new DateTime($options['begin'], $this->dateTimeFactory->getTimezone());
|
$begin = new DateTime($options['begin'], $this->dateTimeFactory->getTimezone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($options['end'] instanceof DateTime) {
|
||||||
|
$end = $options['end'];
|
||||||
|
} else {
|
||||||
$end = new DateTime($options['end'], $this->dateTimeFactory->getTimezone());
|
$end = new DateTime($options['end'], $this->dateTimeFactory->getTimezone());
|
||||||
|
}
|
||||||
|
|
||||||
return $this->repository->getDailyStats($user, $begin, $end);
|
return $this->repository->getDailyStats($user, $begin, $end);
|
||||||
}
|
}
|
||||||
|
|||||||
101
src/Widget/Type/PaginatedWorkingTimeChart.php
Normal file
101
src/Widget/Type/PaginatedWorkingTimeChart.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Widget\Type;
|
||||||
|
|
||||||
|
use App\Repository\TimesheetRepository;
|
||||||
|
use App\Security\CurrentUser;
|
||||||
|
use App\Timesheet\UserDateTimeFactory;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
final class PaginatedWorkingTimeChart extends SimpleWidget
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var TimesheetRepository
|
||||||
|
*/
|
||||||
|
private $repository;
|
||||||
|
/**
|
||||||
|
* @var UserDateTimeFactory
|
||||||
|
*/
|
||||||
|
private $dateTimeFactory;
|
||||||
|
|
||||||
|
public function __construct(TimesheetRepository $repository, CurrentUser $user, UserDateTimeFactory $dateTime)
|
||||||
|
{
|
||||||
|
$this->repository = $repository;
|
||||||
|
$this->dateTimeFactory = $dateTime;
|
||||||
|
$this->setId('PaginatedWorkingTimeChart');
|
||||||
|
$this->setTitle('stats.yourWorkingHours');
|
||||||
|
|
||||||
|
$this->setOptions([
|
||||||
|
'year' => (new DateTime('now', $this->dateTimeFactory->getTimezone()))->format('Y'),
|
||||||
|
'week' => (new DateTime('now', $this->dateTimeFactory->getTimezone()))->format('W'),
|
||||||
|
'user' => $user->getUser(),
|
||||||
|
'type' => 'bar',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOptions(array $options = []): array
|
||||||
|
{
|
||||||
|
$options = parent::getOptions($options);
|
||||||
|
|
||||||
|
if (!in_array($options['type'], ['bar', 'line'])) {
|
||||||
|
$options['type'] = 'bar';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDate($year, $week, $day, $hour, $minute, $second)
|
||||||
|
{
|
||||||
|
$now = new DateTime('now', $this->dateTimeFactory->getTimezone());
|
||||||
|
$now->setISODate($year, $week, $day);
|
||||||
|
$now->setTime($hour, $minute, $second);
|
||||||
|
|
||||||
|
return $now;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData(array $options = [])
|
||||||
|
{
|
||||||
|
$options = $this->getOptions($options);
|
||||||
|
$user = $options['user'];
|
||||||
|
|
||||||
|
$weekBegin = $this->getDate($options['year'], $options['week'], 1, 0, 0, 0);
|
||||||
|
$weekEnd = $this->getDate($options['year'], $options['week'], 7, 23, 59, 59);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'begin' => clone $weekBegin,
|
||||||
|
'end' => clone $weekEnd,
|
||||||
|
'stats' => $this->repository->getDailyStats($user, $weekBegin, $weekEnd),
|
||||||
|
'day' => $this->repository->getStatistic(
|
||||||
|
'duration',
|
||||||
|
new DateTime('00:00:00', $this->dateTimeFactory->getTimezone()),
|
||||||
|
new DateTime('23:59:59', $this->dateTimeFactory->getTimezone()),
|
||||||
|
$user
|
||||||
|
),
|
||||||
|
'week' => $this->repository->getStatistic(
|
||||||
|
'duration',
|
||||||
|
$weekBegin,
|
||||||
|
$weekEnd,
|
||||||
|
$user
|
||||||
|
),
|
||||||
|
'month' => $this->repository->getStatistic(
|
||||||
|
'duration',
|
||||||
|
(clone $weekBegin)->setDate($weekBegin->format('Y'), $weekBegin->format('n'), 1)->setTime(0, 0, 0),
|
||||||
|
(clone $weekBegin)->setDate($weekBegin->format('Y'), $weekBegin->format('n'), $weekBegin->format('t'))->setTime(23, 59, 59),
|
||||||
|
$user
|
||||||
|
),
|
||||||
|
'year' => $this->repository->getStatistic(
|
||||||
|
'duration',
|
||||||
|
new DateTime(sprintf('01 january %s 00:00:00', $options['year']), $this->dateTimeFactory->getTimezone()),
|
||||||
|
new DateTime(sprintf('31 december %s 23:59:59', $options['year']), $this->dateTimeFactory->getTimezone()),
|
||||||
|
$user
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
1
templates/widget/paginatedworkingtimechart.html.twig
Normal file
1
templates/widget/paginatedworkingtimechart.html.twig
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{{ render_widget('PaginatedWorkingTimeChart', {'year': year, 'week': week}) }}
|
||||||
@@ -12,16 +12,15 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ encore_entry_link_tags('chart') }}
|
|
||||||
{{ encore_entry_script_tags('chart') }}
|
|
||||||
|
|
||||||
<div class="chart">
|
<div class="chart">
|
||||||
<canvas id="{{ chart_id }}" style="height: {{ kimai_context.chart.height }}px;"></canvas>
|
<canvas id="{{ chart_id }}" style="height: {{ kimai_context.chart.height }}px;"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
document.addEventListener('kimai.initialized', function() {
|
var myChart = null;
|
||||||
new Chart(
|
|
||||||
|
var paintChart = function() {
|
||||||
|
myChart = new Chart(
|
||||||
document.getElementById('{{ chart_id }}').getContext('2d'), {
|
document.getElementById('{{ chart_id }}').getContext('2d'), {
|
||||||
type: '{{ type }}',
|
type: '{{ type }}',
|
||||||
data: {
|
data: {
|
||||||
@@ -83,5 +82,15 @@ document.addEventListener('kimai.initialized', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
var destroyChart = function () {
|
||||||
|
myChart.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
{% if app.request.xmlHttpRequest %}
|
||||||
|
paintChart();
|
||||||
|
{% else %}
|
||||||
|
document.addEventListener('kimai.initialized', paintChart);
|
||||||
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
74
templates/widget/widget-paginatedworkingtimechart.html.twig
Normal file
74
templates/widget/widget-paginatedworkingtimechart.html.twig
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
|
||||||
|
{% block box_title %}
|
||||||
|
{% if not title is empty %}{{ title|trans }}{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block box_attributes %}
|
||||||
|
id="PaginatedWorkingTimeChart" data-href="#" data-reload=""
|
||||||
|
{% endblock %}
|
||||||
|
{% block box_tools %}
|
||||||
|
<ul class="pagination pagination-sm inline">
|
||||||
|
{% set prevYear = options.year %}
|
||||||
|
{% set prevWeek = options.week %}
|
||||||
|
{% set nextYear = options.year %}
|
||||||
|
{% set nextWeek = options.week %}
|
||||||
|
{% if prevWeek == 1 %}
|
||||||
|
{% set prevYear = prevYear - 1 %}
|
||||||
|
{% set prevWeek = 52 %}
|
||||||
|
{% else %}
|
||||||
|
{% set prevWeek = prevWeek - 1 %}
|
||||||
|
{% endif %}
|
||||||
|
{% if nextWeek == 52 %}
|
||||||
|
{% set nextYear = nextYear + 1 %}
|
||||||
|
{% set nextWeek = 1 %}
|
||||||
|
{% else %}
|
||||||
|
{% set nextWeek = nextWeek + 1 %}
|
||||||
|
{% endif %}
|
||||||
|
<li class="prev"><a onclick="destroyChart()" href="{{ path('widgets_working_time_chart', {'year': prevYear, 'week': prevWeek}) }}" rel="prev">← </a></li>
|
||||||
|
<li class="next"><a onclick="destroyChart()" href="{{ path('widgets_working_time_chart', {'year': nextYear, 'week': nextWeek}) }}" rel="next"> →</a></li>
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
|
{% block box_body %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{{ render_widget('DailyWorkingTimeChart', options|merge({'begin': data.begin, 'end': data.end})) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block box_footer %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3 col-xs-6">
|
||||||
|
<div class="description-block border-right">
|
||||||
|
<h5 class="description-header">{{ data.day|duration }}</h5>
|
||||||
|
<span class="description-text">{{ 'stats.durationToday'|trans }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3 col-xs-6">
|
||||||
|
<div class="description-block border-right">
|
||||||
|
<h5 class="description-header">{{ data.week|duration }}</h5>
|
||||||
|
<span class="description-text">{{ 'stats.durationWeek'|trans }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3 col-xs-6">
|
||||||
|
<div class="description-block border-right">
|
||||||
|
<h5 class="description-header">{{ data.month|duration }}</h5>
|
||||||
|
<span class="description-text">{{ 'stats.durationMonth'|trans }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3 col-xs-6">
|
||||||
|
<div class="description-block border-right">
|
||||||
|
<h5 class="description-header">{{ data.year|duration }}</h5>
|
||||||
|
<span class="description-text">{{ 'stats.durationYear'|trans }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% endembed %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
document.addEventListener('kimai.initialized', function() {
|
||||||
|
KimaiPaginatedBoxWidget.create('#PaginatedWorkingTimeChart');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -101,9 +101,11 @@ class DailyWorkingTimeChartTest extends TestCase
|
|||||||
public function testGetData()
|
public function testGetData()
|
||||||
{
|
{
|
||||||
$repository = $this->getMockBuilder(TimesheetRepository::class)->disableOriginalConstructor()->onlyMethods(['getDailyData'])->getMock();
|
$repository = $this->getMockBuilder(TimesheetRepository::class)->disableOriginalConstructor()->onlyMethods(['getDailyData'])->getMock();
|
||||||
$repository->expects($this->once())->method('getDailyData')->willReturnCallback(function ($user, $begin, $end) {
|
$repository->expects($this->once())->method('getDailyData')->willReturnCallback(function ($begin, $end, $user) {
|
||||||
|
$today = (new \DateTime());
|
||||||
|
|
||||||
return [
|
return [
|
||||||
['year' => '2019', 'month' => '1', 'day' => 1, 'rate' => 13.75, 'duration' => 1234]
|
['year' => $today->format('Y'), 'month' => $today->format('n'), 'day' => $today->format('j'), 'rate' => 13.75, 'duration' => 1234]
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user