diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php index 8f38d94a..8741a4b6 100644 --- a/src/Controller/DashboardController.php +++ b/src/Controller/DashboardController.php @@ -9,16 +9,12 @@ namespace App\Controller; -use App\Entity\Activity; -use App\Entity\Customer; -use App\Entity\Project; -use App\Entity\Timesheet; -use App\Entity\User; -use App\Repository\Query\TimesheetQuery; +use App\Event\DashboardEvent; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Dashboard controller for the admin area. @@ -28,120 +24,34 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; */ class DashboardController extends Controller { + /** + * @var EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * @param EventDispatcherInterface $dispatcher + */ + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->eventDispatcher = $dispatcher; + } + /** * @Route("/", defaults={}, name="dashboard") * @Method("GET") */ public function indexAction() { - $user = $this->getUser(); + $event = new DashboardEvent($this->getUser()); - $userStats = $this->getDoctrine()->getRepository(User::class)->getGlobalStatistics(); - - // FIXME move the other widgets to the Kimai, the inheritence is wrong as Kimai - // shouldn't know about Timesheets - - $timesheetRepo = $this->getDoctrine()->getRepository(Timesheet::class); - $timesheetUserStats = $timesheetRepo->getUserStatistics($user); - $timesheetGlobalStats = $timesheetRepo->getGlobalStatistics(); - - $activityStats = $this->getDoctrine()->getRepository(Activity::class)->getGlobalStatistics(); - $projectStats = $this->getDoctrine()->getRepository(Project::class)->getGlobalStatistics(); - $customerStats = $this->getDoctrine()->getRepository(Customer::class)->getGlobalStatistics(); + $this->eventDispatcher->dispatch( + DashboardEvent::DASHBOARD, + $event + ); return $this->render('dashboard/index.html.twig', [ - 'dashboard_widgets' => $this->getWidgets(), - 'timesheetGlobal' => $timesheetGlobalStats, - 'timesheetUser' => $timesheetUserStats, - 'activity' => $activityStats, - 'project' => $projectStats, - 'customer' => $customerStats, - 'user' => $userStats, + 'widget_rows' => $event->getWidgetRows() ]); } - - /** - * colors: blue / yellow / purple / green / black - * icons: bar-chart / line-chart / calendar / clock - * - * @return array - */ - protected function getWidgets() - { - // @codingStandardsIgnoreStart - $widgets = [ - /* - [ - 'header' => 'dashboard.you', - 'widgets' => [ - "{{ widgets.info_box_progress('Bewilligte Stunden', 'Stunden zur Abrechnung bewilligt', 120, 10, 'star') }}", - "{{ widgets.info_box_progress('Umsatz / Monat', '70% Increase in 30 Days', 6830, 30, 'credit-card', 'black') }}", - "{{ widgets.info_box_progress('Stunden persönlich', 'Das ist noch nicht genug', 135, 60, 'hourglass') }}", - "{{ widgets.info_box_progress('Anzahl Benutzer', 'Mehr ist besser!', 5, 90, 'user') }}", - ], - ], - */ - [ - 'id' => 'profile.stats', - 'header' => 'dashboard.you', - 'widgets' => [ - "{{ widgets.info_box_counter('stats.durationThisMonth', timesheetUser.durationThisMonth|duration(true), 'far fa-hourglass', 'green') }}", - //"{{ widgets.info_box_counter('stats.amountThisMonth', timesheetUser.amountThisMonth|money, 'money', 'blue') }}", - "{{ widgets.info_box_counter('stats.durationTotal', timesheetUser.durationTotal|duration(true), 'far fa-hourglass', 'red') }}", - //"{{ widgets.info_box_counter('stats.amountTotal', timesheetUser.amountTotal|money, 'money', 'yellow') }}", - ], - ], - ]; - - if (!$this->isGranted('ROLE_TEAMLEAD', null)) { - return $widgets; - } - - $widgets[] = [ - 'id' => 'alluser.stats', - 'header' => 'dashboard.all', - 'widgets' => [ - "{{ widgets.info_box_counter('stats.durationThisMonth', timesheetGlobal.durationThisMonth|duration(true), 'far fa-hourglass', 'blue') }}", - "{{ widgets.info_box_counter('stats.durationTotal', timesheetGlobal.durationTotal|duration(true), 'far fa-hourglass', 'yellow') }}", - "{{ widgets.info_box_counter('stats.activeRecordings', timesheetGlobal.activeCurrently, 'far fa-hourglass', 'red', path('admin_timesheet', {'state': " . TimesheetQuery::STATE_RUNNING . '})) }}', - ], - ]; - - $widgets[] = [ - 'id' => 'user.stats', - 'header' => '', - 'widgets' => [ - "{{ widgets.info_box_counter('stats.userTotal', user.totalAmount, 'user', 'red') }}", - "{{ widgets.info_box_counter('stats.userActiveThisMoth', timesheetGlobal.activeThisMonth, 'user', 'yellow') }}", - "{{ widgets.info_box_counter('stats.userActiveEver', timesheetGlobal.activeTotal, 'user', 'blue') }}", - ], - ]; - - if (!$this->isGranted('ROLE_ADMIN', null)) { - return $widgets; - } - - $widgets[] = [ - 'id' => 'alluser.money_stats', - 'header' => '', - 'widgets' => [ - "{{ widgets.info_box_counter('stats.amountThisMonth', timesheetGlobal.amountThisMonth|money, 'far fa-money-bill-alt', 'green') }}", - "{{ widgets.info_box_counter('stats.amountTotal', timesheetGlobal.amountTotal|money, 'far fa-money-bill-alt', 'red') }}", - ], - ]; - - $widgets[] = [ - 'id' => 'admin.stats', - 'header' => 'dashboard.admin', - 'widgets' => [ - "{{ widgets.info_box_more('stats.userTotal', user.totalAmount, ' ', path('admin_user'), 'user') }}", - "{{ widgets.info_box_more('stats.customerTotal', customer.count, '', path('admin_customer'), 'customer', 'blue') }}", - "{{ widgets.info_box_more('stats.projectsTotal', project.count, '', path('admin_project'), 'project', 'yellow') }}", - "{{ widgets.info_box_more('stats.activitiesTotal', activity.count, '', path('admin_activity'), 'activity', 'purple') }}", - ], - ]; - // @codingStandardsIgnoreEnd - - return $widgets; - } } diff --git a/src/Event/DashboardEvent.php b/src/Event/DashboardEvent.php new file mode 100644 index 00000000..e434c733 --- /dev/null +++ b/src/Event/DashboardEvent.php @@ -0,0 +1,59 @@ +user = $user; + } + + /** + * @return User + */ + public function getUser() + { + return $this->user; + } + + public function addWidgetRow(WidgetRow $row) + { + $this->widgetRows[] = $row; + } + + /** + * @return WidgetRow[] + */ + public function getWidgetRows() + { + return $this->widgetRows; + } +} diff --git a/src/EventSubscriber/DashboardSubscriber.php b/src/EventSubscriber/DashboardSubscriber.php new file mode 100644 index 00000000..bf475d5f --- /dev/null +++ b/src/EventSubscriber/DashboardSubscriber.php @@ -0,0 +1,169 @@ +security = $security; + $this->registry = $registry; + } + + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + DashboardEvent::DASHBOARD => ['onDashboardEvent', 100], + ]; + } + + /** + * @param DashboardEvent $event + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function onDashboardEvent(DashboardEvent $event) + { + $timesheetRepo = $this->registry->getRepository(Timesheet::class); + $timesheetGlobal = $timesheetRepo->getGlobalStatistics(); + $timesheetUser = $timesheetRepo->getUserStatistics($event->getUser()); + $userStats = $this->registry->getRepository(User::class)->getGlobalStatistics(); + + $this->addUserWidgets($event, $timesheetUser); + + if (!$this->security->isGranted('ROLE_TEAMLEAD')) { + return; + } + + $this->addTeamleadWidgets($event, $timesheetGlobal, $userStats); + + if (!$this->security->isGranted('ROLE_ADMIN')) { + return; + } + + $this->addAdminWidgets($event, $timesheetGlobal, $userStats); + } + + /** + * @param DashboardEvent $event + * @param TimesheetStatistic $timesheet + */ + protected function addUserWidgets(DashboardEvent $event, TimesheetStatistic $timesheet) + { + /* + $row = new WidgetRow('dashboard.you'); + $widgets = [ + [ + 'widgets' => [ + "{{ widgets.info_box_progress('Bewilligte Stunden', 'Stunden zur Abrechnung bewilligt', 120, 10, 'star') }}", + "{{ widgets.info_box_progress('Umsatz / Monat', '70% Increase in 30 Days', 6830, 30, 'credit-card', 'black') }}", + "{{ widgets.info_box_progress('Stunden persönlich', 'Das ist noch nicht genug', 135, 60, 'hourglass') }}", + "{{ widgets.info_box_progress('Anzahl Benutzer', 'Mehr ist besser!', 5, 90, 'user') }}", + ], + ], + $event->addWidgetRow($row); + */ + + $row = new WidgetRow('profile.stats', 'dashboard.you'); + $row + ->add("{{ widgets.info_box_counter('stats.durationThisMonth', " . $timesheet->getDurationThisMonth() . "|duration(true), 'far fa-hourglass', 'green') }}") + //->add("{{ widgets.info_box_counter('stats.amountThisMonth', ".$timesheet->getAmountThisMonth()."|money, 'money', 'blue') }}") + ->add("{{ widgets.info_box_counter('stats.durationTotal', " . $timesheet->getDurationTotal() . "|duration(true), 'far fa-hourglass', 'red') }}") + //->add("{{ widgets.info_box_counter('stats.amountTotal', ".$timesheet->getAmountTotal()."|money, 'money', 'yellow') }}") + ; + $event->addWidgetRow($row); + } + + /** + * @param DashboardEvent $event + * @param TimesheetGlobalStatistic $timesheet + * @param UserStatistic $userStats + */ + protected function addTeamleadWidgets(DashboardEvent $event, TimesheetGlobalStatistic $timesheet, UserStatistic $userStats) + { + $row = new WidgetRow('alluser.stats', 'dashboard.all'); + $row + ->add("{{ widgets.info_box_counter('stats.durationThisMonth', " . $timesheet->getDurationThisMonth() . "|duration(true), 'far fa-hourglass', 'blue') }}") + ->add("{{ widgets.info_box_counter('stats.durationTotal', " . $timesheet->getDurationTotal() . "|duration(true), 'far fa-hourglass', 'yellow') }}") + ->add("{{ widgets.info_box_counter('stats.activeRecordings', " . $timesheet->getActiveCurrently() . ", 'far fa-hourglass', 'red', path('admin_timesheet', {'state': " . TimesheetQuery::STATE_RUNNING . '})) }}') + ; + $event->addWidgetRow($row); + + $row = new WidgetRow('user.stats'); + $row + ->add("{{ widgets.info_box_counter('stats.userTotal', " . $userStats->getTotalAmount() . ", 'user', 'red') }}") + ->add("{{ widgets.info_box_counter('stats.userActiveThisMoth', " . $timesheet->getActiveThisMonth() . ", 'user', 'yellow') }}") + ->add("{{ widgets.info_box_counter('stats.userActiveEver', " . $timesheet->getActiveTotal() . ", 'user', 'blue') }}") + ; + $event->addWidgetRow($row); + } + + /** + * @param DashboardEvent $event + * @param TimesheetGlobalStatistic $timesheet + * @param UserStatistic $user + * @throws \Doctrine\ORM\NonUniqueResultException + */ + protected function addAdminWidgets(DashboardEvent $event, TimesheetGlobalStatistic $timesheet, UserStatistic $user) + { + $row = new WidgetRow('alluser.money_stats'); + $row + ->add("{{ widgets.info_box_counter('stats.amountThisMonth', " . $timesheet->getAmountThisMonth() . "|money, 'far fa-money-bill-alt', 'green') }}") + ->add("{{ widgets.info_box_counter('stats.amountTotal', " . $timesheet->getAmountTotal() . "|money, 'far fa-money-bill-alt', 'red') }}") + ; + $event->addWidgetRow($row); + + $activity = $this->registry->getRepository(Activity::class)->getGlobalStatistics(); + $project = $this->registry->getRepository(Project::class)->getGlobalStatistics(); + $customer = $this->registry->getRepository(Customer::class)->getGlobalStatistics(); + + $row = new WidgetRow('admin.stats', 'dashboard.admin'); + $row + ->add("{{ widgets.info_box_more('stats.userTotal', " . $user->getTotalAmount() . ", ' ', path('admin_user'), 'user') }}") + ->add("{{ widgets.info_box_more('stats.customerTotal', " . $customer->getCount() . ", '', path('admin_customer'), 'customer', 'blue') }}") + ->add("{{ widgets.info_box_more('stats.projectsTotal', " . $project->getCount() . ", '', path('admin_project'), 'project', 'yellow') }}") + ->add("{{ widgets.info_box_more('stats.activitiesTotal', " . $activity->getCount() . ", '', path('admin_activity'), 'activity', 'purple') }}") + ; + $event->addWidgetRow($row); + } +} diff --git a/src/Model/TimesheetGlobalStatistic.php b/src/Model/TimesheetGlobalStatistic.php index 4a13acbf..b7d1abc1 100644 --- a/src/Model/TimesheetGlobalStatistic.php +++ b/src/Model/TimesheetGlobalStatistic.php @@ -40,7 +40,7 @@ class TimesheetGlobalStatistic extends TimesheetStatistic */ public function setActiveCurrently($activeCurrently) { - $this->activeCurrently = $activeCurrently; + $this->activeCurrently = (int) $activeCurrently; } /** @@ -56,7 +56,7 @@ class TimesheetGlobalStatistic extends TimesheetStatistic */ public function setActiveThisMonth($activeThisMonth) { - $this->activeThisMonth = $activeThisMonth; + $this->activeThisMonth = (int) $activeThisMonth; } /** @@ -72,6 +72,6 @@ class TimesheetGlobalStatistic extends TimesheetStatistic */ public function setActiveTotal($activeTotal) { - $this->activeTotal = $activeTotal; + $this->activeTotal = (int) $activeTotal; } } diff --git a/src/Model/TimesheetStatistic.php b/src/Model/TimesheetStatistic.php index 4cfd07e1..d3defd52 100644 --- a/src/Model/TimesheetStatistic.php +++ b/src/Model/TimesheetStatistic.php @@ -50,7 +50,7 @@ class TimesheetStatistic */ public function setDurationThisMonth($durationThisMonth) { - $this->durationThisMonth = $durationThisMonth; + $this->durationThisMonth = (int) $durationThisMonth; } /** @@ -66,7 +66,7 @@ class TimesheetStatistic */ public function setAmountTotal($amountTotal) { - $this->amountTotal = $amountTotal; + $this->amountTotal = (int) $amountTotal; } /** @@ -82,7 +82,7 @@ class TimesheetStatistic */ public function setDurationTotal($durationTotal) { - $this->durationTotal = $durationTotal; + $this->durationTotal = (int) $durationTotal; } /** @@ -98,7 +98,7 @@ class TimesheetStatistic */ public function setAmountThisMonth($amountThisMonth) { - $this->amountThisMonth = $amountThisMonth; + $this->amountThisMonth = (int) $amountThisMonth; } /** diff --git a/src/Model/UserStatistic.php b/src/Model/UserStatistic.php index 7e28dede..4fb9a326 100644 --- a/src/Model/UserStatistic.php +++ b/src/Model/UserStatistic.php @@ -32,6 +32,6 @@ class UserStatistic */ public function setTotalAmount($totalAmount) { - $this->totalAmount = $totalAmount; + $this->totalAmount = (int) $totalAmount; } } diff --git a/src/Model/WidgetRow.php b/src/Model/WidgetRow.php new file mode 100644 index 00000000..9e701932 --- /dev/null +++ b/src/Model/WidgetRow.php @@ -0,0 +1,71 @@ +id = $id; + $this->title = $title; + } + + /** + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @return string[] + */ + public function getWidgets(): array + { + return $this->widgets; + } + + /** + * @param string $templateString + * @return $this + */ + public function add(string $templateString) + { + $this->widgets[] = $templateString; + + return $this; + } +} diff --git a/templates/dashboard/index.html.twig b/templates/dashboard/index.html.twig index c65221ac..6d705413 100644 --- a/templates/dashboard/index.html.twig +++ b/templates/dashboard/index.html.twig @@ -8,19 +8,24 @@ {% block main %} - {% for settings in dashboard_widgets %} - {% if settings.header %} - {{ widgets.page_header(settings.header) }} + {% for row in widget_rows %} + {% if row.title %} + {{ widgets.page_header(row.title) }} {% endif %} - {% set width = settings.widgets|length %} + {% set width = row.widgets|length %} {% set rawWidth = 12 / width %} {% set columnWidth = rawWidth|round(0, 'floor') %}
- {% for widgetTemplate in settings.widgets %} + {% for widgetTemplate in row.widgets %}
- {% set widgetString = '{% import "macros/widgets.html.twig" as widgets %}' ~ widgetTemplate %} - {{ include(template_from_string(widgetString)) }} + {{ + include( + template_from_string( + '{% import "macros/widgets.html.twig" as widgets %}' ~ widgetTemplate + ) + ) + }}
{% endfor %}
diff --git a/tests/Controller/DashboardControllerTest.php b/tests/Controller/DashboardControllerTest.php index 4c7d4079..f5cac1f3 100644 --- a/tests/Controller/DashboardControllerTest.php +++ b/tests/Controller/DashboardControllerTest.php @@ -9,6 +9,8 @@ namespace App\Tests\Controller; +use App\Entity\User; + /** * @coversDefaultClass \App\Controller\DashboardController * @group integration @@ -27,4 +29,12 @@ class DashboardControllerTest extends ControllerBaseTest $this->assertTrue($client->getResponse()->isSuccessful()); $this->assertMainContentClass($client, 'dashboard'); } + + public function testIndexActionForAdmin() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); + $this->request($client, '/dashboard/'); + $this->assertTrue($client->getResponse()->isSuccessful()); + $this->assertMainContentClass($client, 'dashboard'); + } } diff --git a/var/docs/developers.md b/var/docs/developers.md index 2d4103f5..3d178c38 100644 --- a/var/docs/developers.md +++ b/var/docs/developers.md @@ -100,8 +100,8 @@ The files in `translations/` as a quick overview: If you want to add your own entries in the navigation bar, you can subscribe to these events: -- `App\EventConfigureMainMenuEvent::CONFIGURE` -- `App\ConfigureAdminMenuEvent::CONFIGURE` +- `App\Event\ConfigureMainMenuEvent::CONFIGURE` +- `App\Event\ConfigureAdminMenuEvent::CONFIGURE` And that's how to use it: @@ -111,7 +111,7 @@ use App\Event\ConfigureAdminMenuEvent; use Avanzu\AdminThemeBundle\Model\MenuItemModel; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -class MySubscriber implements EventSubscriberInterface +class MyMenuSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { @@ -138,6 +138,37 @@ class MySubscriber implements EventSubscriberInterface ``` For more details check the [official menu subscriber](../../src/EventSubscriber/MenuSubscriber.php). +## Extending the dashboard with widgets + +If you want to add your own widget rows to the dashboard, you can subscribe to the event: + +- `App\Event\DashboardEvent::DASHBOARD` + +And that's how to use it: + +```php +use App\Event\DashboardEvent; +use App\Model\WidgetRow; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class MyDashboardSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [DashboardEvent::DASHBOARD => ['onDashboardEvent', 200]]; + } + + public function onDashboardEvent(DashboardEvent $event) + { + $row = new WidgetRow('my_id', 'optional.row.title'); + // this needs to be a valid twig template string + $row->add("{{ widgets.info_box_counter('a title', 100, 'far fa-hourglass', 'green') }}"); + $event->addWidgetRow($row); + } +} +``` +For more details check the [official dashboard subscriber](../../src/EventSubscriber/DashboardSubscriber.php). + ## Adding tabs to the "control sidebar" We use twig globals to render the control sidebar tabs, so adding one is as easy as adding a new config entry: