users weekly stats as bar-chart in dashboard (#847)

This commit is contained in:
Kevin Papst
2019-06-13 18:38:49 +02:00
committed by GitHub
parent e1f862507e
commit 3311f17bbb
96 changed files with 2754 additions and 846 deletions

View File

@@ -0,0 +1,42 @@
<?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\Renderer;
use App\Widget\WidgetRendererInterface;
use Twig\Environment;
abstract class AbstractTwigRenderer implements WidgetRendererInterface
{
/**
* @var Environment
*/
protected $twig;
/**
* @param Environment $twig
*/
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
/**
* @param string $template
* @param array $data
* @return string
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
protected function renderTemplate(string $template, array $data): string
{
return $this->twig->render($template, $data);
}
}

View File

@@ -0,0 +1,29 @@
<?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\Renderer;
use App\Widget\Type\CompoundChart;
use App\Widget\WidgetInterface;
class CompoundChartRenderer extends AbstractTwigRenderer
{
public function supports(WidgetInterface $widget): bool
{
return ($widget instanceof CompoundChart);
}
public function render(WidgetInterface $widget, array $options = []): string
{
return $this->renderTemplate('widget/section-chart.html.twig', [
'title' => $widget->getTitle(),
'widgets' => $widget->getData(),
]);
}
}

View File

@@ -0,0 +1,29 @@
<?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\Renderer;
use App\Widget\Type\CompoundRow;
use App\Widget\WidgetInterface;
class CompoundRowRenderer extends AbstractTwigRenderer
{
public function supports(WidgetInterface $widget): bool
{
return ($widget instanceof CompoundRow);
}
public function render(WidgetInterface $widget, array $options = []): string
{
return $this->renderTemplate('widget/section-simple.html.twig', [
'title' => $widget->getTitle(),
'widgets' => $widget->getData(),
]);
}
}

View File

@@ -0,0 +1,33 @@
<?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\Renderer;
use App\Widget\Type\SimpleWidget;
use App\Widget\WidgetInterface;
use ReflectionClass;
class SimpleWidgetRenderer extends AbstractTwigRenderer
{
public function supports(WidgetInterface $widget): bool
{
return $widget instanceof SimpleWidget;
}
public function render(WidgetInterface $widget, array $options = []): string
{
$name = (new ReflectionClass($widget))->getShortName();
return $this->renderTemplate(sprintf('widget/widget-%s.html.twig', strtolower($name)), [
'data' => $widget->getData($options),
'options' => $widget->getOptions($options),
'title' => $widget->getTitle(),
]);
}
}

View File

@@ -0,0 +1,95 @@
<?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\Widget\WidgetContainerInterface;
use App\Widget\WidgetInterface;
use BadMethodCallException;
abstract class AbstractContainer implements WidgetContainerInterface
{
/**
* @var string
*/
protected $title = '';
/**
* @var WidgetInterface[]
*/
protected $widgets = [];
/**
* @var int
*/
protected $order = 0;
public function setTitle(string $title)
{
$this->title = $title;
}
/**
* @param array $options
* @return WidgetInterface[]|array|mixed|null
*/
public function getData(array $options = [])
{
return $this->getWidgets();
}
public function getOptions(array $options = []): array
{
return $options;
}
/**
* @param string $name
* @param mixed $value
*/
public function setOption(string $name, $value): void
{
throw new BadMethodCallException('setOption() is not supported on AbstractContainer');
}
public function getId(): string
{
return $this->title;
}
public function getOrder(): int
{
return $this->order;
}
public function setOrder(int $order)
{
$this->order = $order;
return $this;
}
public function getTitle(): string
{
return $this->title;
}
/**
* @return WidgetInterface[]
*/
public function getWidgets(): array
{
return $this->widgets;
}
public function addWidget(WidgetInterface $widget)
{
$this->widgets[] = $widget;
return $this;
}
}

View File

@@ -0,0 +1,109 @@
<?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\Widget\WidgetInterface;
abstract class AbstractWidgetType implements WidgetInterface
{
/**
* @var string
*/
protected $id = '';
/**
* @var string
*/
protected $title = '';
/**
* @var array
*/
protected $options = [];
/**
* @var mixed
*/
protected $data;
public function setId(string $id): AbstractWidgetType
{
$this->id = $id;
return $this;
}
public function getId(): string
{
return $this->id;
}
public function setData($data): AbstractWidgetType
{
$this->data = $data;
return $this;
}
/**
* @param array $options
* @return mixed|null
*/
public function getData(array $options = [])
{
return $this->data;
}
public function setTitle(string $title): AbstractWidgetType
{
$this->title = $title;
return $this;
}
public function getTitle(): string
{
return $this->title;
}
public function setOptions(array $options): AbstractWidgetType
{
foreach ($options as $key => $value) {
$this->options[$key] = $value;
}
return $this;
}
/**
* @param string $name
* @param mixed $value
*/
public function setOption(string $name, $value): void
{
$this->options[$name] = $value;
}
/**
* @param string $name
* @param mixed|null $default
* @return mixed|null
*/
public function getOption(string $name, $default = null)
{
if (array_key_exists($name, $this->options)) {
return $this->options[$name];
}
return $default;
}
public function getOptions(array $options = []): array
{
return array_merge($this->options, $options);
}
}

View File

@@ -0,0 +1,14 @@
<?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;
class CompoundChart extends AbstractContainer
{
}

View File

@@ -0,0 +1,14 @@
<?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;
class CompoundRow extends AbstractContainer
{
}

View File

@@ -0,0 +1,18 @@
<?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;
class Counter extends SimpleWidget
{
public function __construct()
{
$this->setOption('dataType', 'int');
}
}

View File

@@ -0,0 +1,65 @@
<?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 DateTime;
class DailyWorkingTimeChart extends SimpleWidget
{
public const DEFAULT_CHART = 'bar';
/**
* @var TimesheetRepository
*/
protected $repository;
public function __construct(TimesheetRepository $repository, CurrentUser $user)
{
$this->repository = $repository;
$this->setId('DailyWorkingTimeChart');
$this->setTitle('stats.yourWorkingHours');
$this->setOptions([
'begin' => 'monday this week 00:00:00',
'end' => 'sunday this week 23:59:59',
'color' => '',
'user' => $user->getUser(),
'type' => self::DEFAULT_CHART,
'id' => '',
]);
}
public function getOptions(array $options = []): array
{
$options = parent::getOptions($options);
if (!in_array($options['type'], ['bar', 'line'])) {
$options['type'] = self::DEFAULT_CHART;
}
if (empty($options['id'])) {
$options['id'] = uniqid('DailyWorkingTimeChart_');
}
return $options;
}
public function getData(array $options = [])
{
$options = $this->getOptions($options);
$user = $options['user'];
$begin = new DateTime($options['begin']);
$end = new DateTime($options['end']);
return $this->repository->getDailyStats($user, $begin, $end);
}
}

18
src/Widget/Type/More.php Normal file
View File

@@ -0,0 +1,18 @@
<?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;
class More extends SimpleWidget
{
public function __construct()
{
$this->setOption('dataType', 'int');
}
}

View File

@@ -0,0 +1,14 @@
<?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;
class SimpleWidget extends AbstractWidgetType
{
}

View File

@@ -0,0 +1,14 @@
<?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;
class YearChart extends SimpleWidget
{
}

View File

@@ -0,0 +1,24 @@
<?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;
interface WidgetContainerInterface extends WidgetInterface
{
public function getOrder(): int;
public function setOrder(int $order);
/**
* @return WidgetInterface[]
*/
public function getWidgets(): array;
public function addWidget(WidgetInterface $widget);
}

View File

@@ -0,0 +1,14 @@
<?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;
class WidgetException extends \Exception
{
}

View File

@@ -0,0 +1,65 @@
<?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;
interface WidgetInterface
{
/**
* Returns a unique ID for this widget.
*
* @return string
*/
public function getId(): string;
/**
* Returns the widget title.
*
* If no title is necessary, return an empty string.
*
* @return string
*/
public function getTitle(): string;
/**
* Returns the widgets data, to be used in the frontend rendering.
*
* If your widget relies on options to dynamically change the result data,
* make sure that the given $options will overwrite the internal option for
* this one call.
*
* @param array $options
* @return mixed|null
*/
public function getData(array $options = []);
/**
* Returns all widget options to be used in the frontend.
*
* The given $options are not meant to be persisted, but only to
* overwrite the default values one time.
*
* You can validate the options or simply return:
* return array_merge($this->options, $options);
*
* @param array $options
* @return array
*/
public function getOptions(array $options = []): array;
/**
* Sets one widget option, both for internal use and for frontend rendering.
*
* The given option should be persisted and permanently overwrite the internal option.
*
* @param string $name
* @param mixed $value
*/
public function setOption(string $name, $value): void;
}

View 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\Widget;
interface WidgetRendererInterface
{
/**
* Checks if the given widget can be rendered.
*
* Most renderer check something like:
* return $widget instanceof MyWidgetType;
*
* @param WidgetInterface $widget
* @return bool
*/
public function supports(WidgetInterface $widget): bool;
/**
* Renders the given widget.
*
* The given $options array overwrites the widgets internal options for this call.
*
* @param WidgetInterface $widget
* @param array $options
* @return string
*/
public function render(WidgetInterface $widget, array $options = []): string;
}

View File

@@ -0,0 +1,81 @@
<?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;
use App\Repository\WidgetRepository;
class WidgetService
{
/**
* @var WidgetRendererInterface[]
*/
protected $renderer = [];
/**
* @var WidgetRepository
*/
protected $repository;
/**
* @param WidgetRepository $repository
* @param WidgetRendererInterface[] $renderer
*/
public function __construct(WidgetRepository $repository, iterable $renderer)
{
foreach ($renderer as $render) {
$this->addRenderer($render);
}
$this->repository = $repository;
}
/**
* @param string $widget
* @return bool
*/
public function hasWidget(string $widget): bool
{
return $this->repository->has($widget);
}
public function getWidget(string $widget): WidgetInterface
{
return $this->repository->get($widget);
}
public function addRenderer(WidgetRendererInterface $renderer): WidgetService
{
$this->renderer[] = $renderer;
return $this;
}
/**
* @param WidgetInterface $widget
* @return WidgetRendererInterface
* @throws WidgetException
*/
public function findRenderer(WidgetInterface $widget): WidgetRendererInterface
{
foreach ($this->renderer as $renderer) {
if ($renderer->supports($widget)) {
return $renderer;
}
}
throw new WidgetException(sprintf('No renderer available for widget "%s"', get_class($widget)));
}
/**
* @return WidgetRendererInterface[]
*/
public function getRenderer(): array
{
return $this->renderer;
}
}