improvements

This commit is contained in:
Kevin Papst
2016-10-30 23:39:55 +01:00
parent 55ac11d517
commit 1678129fe9
47 changed files with 4390 additions and 1746 deletions

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Controller\Admin;
use AppBundle\Entity\User;
use Pagerfanta\Pagerfanta;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* Controller used to manage users in the admin part of the site.
*
* @Route("/admin/user")
* @Security("has_role('ROLE_ADMIN')")
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class UserController extends Controller
{
/**
* @Route("/", defaults={"page": 1}, name="admin_user")
* @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_user_paginated")
* @Method("GET")
* @Cache(smaxage="10")
*/
public function indexAction($page)
{
/* @var $entries Pagerfanta */
$entries = $this->getDoctrine()->getRepository(User::class)->findAll($page);
return $this->render('admin/user.html.twig', ['entries' => $entries]);
}
}

View File

@@ -11,6 +11,9 @@
namespace AppBundle\Controller;
use AppBundle\Entity\User;
use TimesheetBundle\Entity\Activity;
use TimesheetBundle\Entity\Project;
use TimesheetBundle\Entity\Timesheet;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
@@ -34,6 +37,22 @@ class DashboardController extends Controller
*/
public function indexAction()
{
return $this->render('dashboard/index.html.twig', []);
$user = $this->getUser();
$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();
$userStats = $this->getDoctrine()->getRepository(User::class)->getGlobalStatistics();
return $this->render('dashboard/index.html.twig', [
'timesheetGlobal' => $timesheetGlobalStats,
'timesheetUser' => $timesheetUserStats,
'activity' => $activityStats,
'project' => $projectStats,
'user' => $userStats,
]);
}
}

View File

@@ -35,37 +35,39 @@ class ProfileController extends Controller
*/
public function indexAction($ident)
{
$user = $this->getUser();
$isAdmin = false;
if ($isAdmin) {
} else {
}
$timesheetRepo = $this->getDoctrine()->getRepository(Timesheet::class);
$userStats = $timesheetRepo->getUserStatistics($user);
// FIXME fetch values dynamically and add trans filter to macros
$items = [
[
'title' => 'Projects',
'title' => 'Stundenlohn',
'url' => '#',
'color' => 'blue',
'amount' => 12
'color' => 'blue', // aqua, red, green
'value' => 70
],
[
'title' => 'Tasks',
'title' => 'Sprache',
'url' => '#',
'color' => 'aqua',
'amount' => 5
],
[
'title' => 'Projects',
'url' => '#',
'color' => 'red',
'amount' => 27
],
[
'title' => 'Einträge',
'url' => '#',
'color' => 'green',
'amount' => 842
'color' => 'green', // aqua, red, green
'value' => 'deutsch'
],
];
return $this->render(
'user/profile.html.twig',
['user' => $this->getUser(), 'items' => $items]
[
'user' => $this->getUser(),
'settings' => $items,
'stats' => $userStats
]
);
}
}

View File

@@ -10,30 +10,37 @@ use Symfony\Component\Security\Core\User\UserInterface;
* User
*
* @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
* @ORM\Table(name="users", uniqueConstraints={@ORM\UniqueConstraint(name="name", columns={"name"}), @ORM\UniqueConstraint(name="apikey", columns={"apikey"})})
* @ORM\Entity
* @ORM\Table(name="users", uniqueConstraints={@ORM\UniqueConstraint(name="name", columns={"name"})})
*/
class User implements UserInterface
{
/**
* @var int
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(name="userID", type="integer")
* @ORM\Column(name="id", type="integer")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=160, nullable=false, unique=true)
*/
private $username;
/**
* @var string
*
* @ORM\Column(name="mail", type="string", length=160, nullable=false, unique=true)
*/
private $email;
/**
* @var string
*
* @ORM\Column(name="password", type="string", length=254, nullable=true)
*/
private $password;
@@ -45,89 +52,26 @@ class User implements UserInterface
*/
private $alias;
/**
* FIXME remove me
*
* @var boolean
*
* @ORM\Column(name="trash", type="boolean", nullable=false)
*/
private $trash = '0';
/**
* @var boolean
*
* @ORM\Column(name="active", type="boolean", nullable=false)
*/
private $active = '1';
private $active = true;
/**
* @var string
* @var \DateTime
*
* @ORM\Column(name="passwordResetHash", type="string", length=32, nullable=true)
* @ORM\Column(name="start_timeframe", type="datetime", nullable=true)
*/
private $passwordresethash;
private $timeframeBegin;
/**
* @var string
* @var \DateTime
*
* @ORM\Column(name="secure", type="string", length=60, nullable=false)
* @ORM\Column(name="end_timeframe", type="datetime", nullable=true)
*/
private $secure = '0';
/**
* FIXME manytoone relation
*
* @var integer
*
* @ORM\Column(name="lastProject", type="integer", nullable=false)
*/
private $lastproject = '1';
/**
* FIXME manytoone
*
* @var integer
*
* @ORM\Column(name="lastActivity", type="integer", nullable=false)
*/
private $lastactivity = '1';
/**
* FIXME manytoone
*
* @var integer
*
* @ORM\Column(name="lastRecord", type="integer", nullable=false)
*/
private $lastrecord = '0';
/**
* @var string
*
* @ORM\Column(name="timeframeBegin", type="string", length=60, nullable=false)
*/
private $timeframebegin = '0';
/**
* @var string
*
* @ORM\Column(name="timeframeEnd", type="string", length=60, nullable=false)
*/
private $timeframeend = '0';
/**
* @var string
*
* @ORM\Column(name="apikey", type="string", length=30, nullable=true)
*/
private $apikey;
// ======= new columns for kimai 2 =======
/**
* @ORM\Column(type="json_array")
*/
private $roles = [];
private $timeframeEnd;
/**
* @var string
@@ -144,11 +88,55 @@ class User implements UserInterface
private $avatar;
/**
* Set alias
* @var string[]
*
* @ORM\Column(type="json_array")
*/
private $roles = [];
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getTimeframeBegin()
{
return $this->timeframeBegin;
}
/**
* @param string $timeframeBegin
*/
public function setTimeframeBegin($timeframeBegin)
{
$this->timeframeBegin = $timeframeBegin;
}
/**
* @return string
*/
public function getTimeframeEnd()
{
return $this->timeframeEnd;
}
/**
* @param string $timeframeEnd
*/
public function setTimeframeEnd($timeframeEnd)
{
$this->timeframeEnd = $timeframeEnd;
}
/**
* @param string $alias
*
* @return User
* @return $this
*/
public function setAlias($alias)
{
@@ -158,8 +146,6 @@ class User implements UserInterface
}
/**
* Get alias
*
* @return string
*/
public function getAlias()
@@ -168,35 +154,8 @@ class User implements UserInterface
}
/**
* Set trash
*
* @param boolean $trash
*
* @return User
*/
public function setTrash($trash)
{
$this->trash = $trash;
return $this;
}
/**
* Get trash
*
* @return boolean
*/
public function getTrash()
{
return $this->trash;
}
/**
* Set active
*
* @param boolean $active
*
* @return User
* @return $this
*/
public function setActive($active)
{
@@ -206,8 +165,6 @@ class User implements UserInterface
}
/**
* Get active
*
* @return boolean
*/
public function getActive()
@@ -216,11 +173,8 @@ class User implements UserInterface
}
/**
* Set password
*
* @param string $password
*
* @return User
* @return $this
*/
public function setPassword($password)
{
@@ -240,273 +194,82 @@ class User implements UserInterface
}
/**
* Set passwordresethash
*
* @param string $passwordresethash
*
* @return User
*/
public function setPasswordresethash($passwordresethash)
{
$this->passwordresethash = $passwordresethash;
return $this;
}
/**
* Get passwordresethash
*
* @return string
*/
public function getPasswordresethash()
{
return $this->passwordresethash;
}
/**
* Set ban
*
* @param integer $ban
*
* @return User
*/
public function setBan($ban)
{
$this->ban = $ban;
return $this;
}
/**
* Get ban
*
* @return integer
*/
public function getBan()
{
return $this->ban;
}
/**
* Set bantime
*
* @param integer $bantime
*
* @return User
*/
public function setBantime($bantime)
{
$this->bantime = $bantime;
return $this;
}
/**
* Get bantime
*
* @return integer
*/
public function getBantime()
{
return $this->bantime;
}
/**
* Set secure
*
* @param string $secure
*
* @return User
*/
public function setSecure($secure)
{
$this->secure = $secure;
return $this;
}
/**
* Get secure
*
* @return string
*/
public function getSecure()
{
return $this->secure;
}
/**
* Set lastproject
*
* @param integer $lastproject
*
* @return User
*/
public function setLastproject($lastproject)
{
$this->lastproject = $lastproject;
return $this;
}
/**
* Get lastproject
*
* @return integer
*/
public function getLastproject()
{
return $this->lastproject;
}
/**
* Set lastactivity
*
* @param integer $lastactivity
*
* @return User
*/
public function setLastactivity($lastactivity)
{
$this->lastactivity = $lastactivity;
return $this;
}
/**
* Get lastactivity
*
* @return integer
*/
public function getLastactivity()
{
return $this->lastactivity;
}
/**
* Set lastrecord
*
* @param integer $lastrecord
*
* @return User
*/
public function setLastrecord($lastrecord)
{
$this->lastrecord = $lastrecord;
return $this;
}
/**
* Get lastrecord
*
* @return integer
*/
public function getLastrecord()
{
return $this->lastrecord;
}
/**
* Set timeframebegin
*
* @param string $timeframebegin
*
* @return User
*/
public function setTimeframebegin($timeframebegin)
{
$this->timeframebegin = $timeframebegin;
return $this;
}
/**
* Get timeframebegin
*
* @return string
*/
public function getTimeframebegin()
{
return $this->timeframebegin;
}
/**
* Set timeframeend
*
* @param string $timeframeend
*
* @return User
*/
public function setTimeframeend($timeframeend)
{
$this->timeframeend = $timeframeend;
return $this;
}
/**
* Get timeframeend
*
* @return string
*/
public function getTimeframeend()
{
return $this->timeframeend;
}
/**
* Set apikey
*
* @param string $apikey
*
* @return User
*/
public function setApikey($apikey)
{
$this->apikey = $apikey;
return $this;
}
/**
* Get apikey
*
* @return string
*/
public function getApikey()
{
return $this->apikey;
}
public function getId()
{
return $this->id;
}
/**
* {@inheritdoc}
*/
public function getUsername()
{
return $this->username;
}
/**
* @param string $username
* @return $this
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* @param string $email
* @return $this
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* @param string $title
* @return $this
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* @return string
*/
public function getAvatar()
{
return $this->avatar;
}
/**
* @param string $avatar
* @return $this
*/
public function setAvatar($avatar)
{
$this->avatar = $avatar;
return $this;
}
/**
* Returns the roles or permissions granted to the user for security.
*
* @return string[]
*/
public function getRoles()
{
@@ -520,9 +283,14 @@ class User implements UserInterface
return array_unique($roles);
}
/**
* @param string[] $roles
* @return $this
*/
public function setRoles(array $roles)
{
$this->roles = $roles;
return $this;
}
/**
@@ -549,35 +317,6 @@ class User implements UserInterface
/**
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* @param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* @return string
*/
public function getAvatar()
{
return $this->avatar;
}
/**
* @param string $avatar
*/
public function setAvatar($avatar)
{
$this->avatar = $avatar;
}
public function __toString()
{
return $this->getAlias() ?: $this->getUsername();

View File

@@ -61,7 +61,7 @@ class MenuBuilder
$isAdmin = $isLoggedIn && $this->security->isGranted('ROLE_ADMIN');
$event->addItem(
new MenuItemModel('dashboard', 'menu.homepage', 'dashboard', [], 'fa fa-home')
new MenuItemModel('dashboard', 'menu.homepage', 'dashboard', [], 'fa fa-dashboard') // fa-home
);
$this->eventDispatcher->dispatch(
@@ -74,8 +74,10 @@ class MenuBuilder
);
if ($isAdmin) {
$event->addItem(
new MenuItemModel('admin', 'menu.admin', '', [], 'fa fa-dashboard')
$admin = new MenuItemModel('admin', 'menu.admin', '', [], 'fa fa-wrench');
$event->addItem($admin);
$admin->addChild(
new MenuItemModel('user_admin', 'menu.admin_user', 'admin_user', [], 'fa fa-user')
);
$this->eventDispatcher->dispatch(

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AppBundle\Model;
/**
* User statistics
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class UserStatistic
{
/**
* @var int
*/
protected $totalAmount = 0;
/**
* @return int
*/
public function getTotalAmount()
{
return $this->totalAmount;
}
/**
* @param int $totalAmount
*/
public function setTotalAmount($totalAmount)
{
$this->totalAmount = $totalAmount;
}
}

View File

@@ -1,9 +1,9 @@
<?php
/*
* This file is part of the Symfony package.
* This file is part of the Kimai package.
*
* (c) Fabien Potencier <fabien@symfony.com>
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
@@ -11,18 +11,76 @@
namespace AppBundle\Repository;
use AppBundle\Model\UserStatistic;
use Doctrine\ORM\Query;
use Doctrine\ORM\EntityRepository;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
/**
* Class UserRepository
*
* This custom Doctrine repository is empty because so far we don't need any custom
* method to query for application user information. But it's always a good practice
* to define a custom repository that will be used when the application grows.
* See http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes
*
* @author Ryan Weaver <weaverryan@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class UserRepository extends EntityRepository
{
/**
* Return statistic data for all user.
*
* @return UserStatistic
*/
public function getGlobalStatistics()
{
$countAll = $this->getEntityManager()
->createQuery('SELECT COUNT(u.id) FROM AppBundle:User u')
->getSingleScalarResult();
$stats = new UserStatistic();
$stats->setTotalAmount($countAll);
return $stats;
}
/**
* @return Query
*/
protected function queryAll()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('u')
->from('AppBundle:User', 'u')
->orderBy('u.id', 'ASC');
return $qb->getQuery();
}
public function findByUsername($username)
{
return $this->findOneBy(['']);
}
/**
* @param int $page
*
* @return Pagerfanta
*/
public function findAll($page = 1)
{
return $this->getPager($this->queryAll(), $page);
}
/**
* @param Query $query
* @param int $page
* @return Pagerfanta
*/
protected function getPager(Query $query, $page = 1)
{
$paginator = new Pagerfanta(new DoctrineORMAdapter($query, false));
$paginator->setMaxPerPage(25);
$paginator->setCurrentPage($page);
return $paginator;
}
}

View File

@@ -13,11 +13,11 @@ namespace AppBundle\Twig;
use AppBundle\Utils\Markdown;
use Symfony\Component\Intl\Intl;
use DateTime;
use DateInterval;
/**
* This Twig extension adds new filters:
* - 'md2html' to transform Markdown contents into HTML contents
* - 'gmdate' to transform timestamps into a gmdate() formatted string
* Multiple Twig extensions: filters and functions
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
@@ -51,7 +51,8 @@ class Extensions extends \Twig_Extension
{
return [
new \Twig_SimpleFilter('md2html', [$this, 'markdownToHtml'], ['is_safe' => ['html']]),
new \Twig_SimpleFilter('gmdate', array($this, 'gmdate')),
new \Twig_SimpleFilter('duration', array($this, 'duration')),
new \Twig_SimpleFilter('money', array($this, 'money')),
];
}
@@ -66,15 +67,37 @@ class Extensions extends \Twig_Extension
}
/**
* Transform a timestamp into a gmdate() formatted string.
* Transforms seconds into a duration string.
*
* @param $seconds
* @param string $format
* @return false|string
* @param bool $includeSeconds
* @return string
*/
public function gmdate($seconds, $format = 'H:i')
public function duration($seconds, $includeSeconds = false)
{
return gmdate($format, $seconds);
$hour = floor($seconds / 3600);
$minute = floor(($seconds / 60) % 60);
$hour = $hour > 9 ? $hour : '0' . $hour;
$minute = $minute > 9 ? $minute : '0' . $minute;
if (!$includeSeconds) {
return $hour . ':' . $minute;
}
$second = $seconds % 60;
$second = $second > 9 ? $second : '0' . $second;
return $hour . ':' . $minute . ':' . $second;
}
/**
* @param float $amount
* @return string
*/
public function money($amount)
{
return round($amount) . ' €';
}
/**

View File

@@ -42,4 +42,16 @@ class TimesheetController extends Controller
return $this->render('TimesheetBundle:timesheet:index.html.twig', ['entries' => $entries]);
}
public function statusEntryAction()
{
$user = $this->getUser();
$activeEntry = $this->getDoctrine()->getRepository(Timesheet::class)->getActiveEntry($user);
$activeEntry = null;
return $this->render(
'TimesheetBundle:Sidebar:navbar-panel.html.twig',
['entry' => $activeEntry]
);
}
}

View File

@@ -35,6 +35,8 @@ class LoadFixtures extends AppBundleLoadFixtures
const AMOUNT_TIMESHEET = 1000; // timesheet entries total
const AMOUNT_PROJECTS = 20; // projects entries total
const AMOUNT_CUSTOMER = 10; // customer entries total
const RATE_MIN = 10; // minimum rate for one hour
const RATE_MAX = 80; // maximum rate for one hour
/**
* {@inheritdoc}
@@ -96,33 +98,33 @@ class LoadFixtures extends AppBundleLoadFixtures
$amountUser = count($allUser);
$allActivity = $this->getAllActivities($manager);
$amountActivity = count($allActivity);
for ($i = 0; $i <= self::AMOUNT_TIMESHEET; $i++) {
$startDay = round($i / 2);
$start = new \DateTime();
$start = $start->modify('- ' . (rand(1, 400)) . ' days');
$start = $start->modify('- ' . (rand(1, $startDay)) . ' days');
$start = $start->modify('- ' . (rand(1, 86400)) . ' seconds');
$end = clone $start;
$end = $end->modify('+ '.(rand(1, 43200)).' seconds');
//$duration = $end->modify('- ' . $start->getTimestamp() . ' seconds')->getTimestamp();
$duration = $end->getTimestamp() - $start->getTimestamp();
$rate = rand(self::RATE_MIN, self::RATE_MAX);
$entry = new Timesheet();
$entry->setActivity($allActivity[array_rand($allActivity)]);
$entry->setStatusid(1); // TODO
$entry->setBillable($i % 2 == 0);
$entry->setBudget(0);
$entry->setCleared($i % 7 == 0);
$entry->setComment($this->getRandomPhrase());
$entry->setDescription($this->getRandomPhrase());
$entry->setLocation($this->getRandomLocation());
$entry->setStart($start->getTimestamp());
$entry->setEnd($end->getTimestamp());
$entry->setDuration($end->modify('- ' . $start->getTimestamp() . ' seconds')->getTimestamp());
$entry->setUser($allUser[rand(1, $amountUser)]);
//$entry->setApproved(false); // TODO
//$entry->setFixedrate(); // TODO
//$entry->setRate(); // TODO
//$entry->setTrackingnumber(); // TODO
$entry->setRate(round(($duration / 3600) * $rate));
$entry->setBegin($start);
// leave one running time entry
if ($i < self::AMOUNT_TIMESHEET) {
$entry->setEnd($end);
$entry->setDuration($duration);
}
$manager->persist($entry);
}
@@ -137,7 +139,7 @@ class LoadFixtures extends AppBundleLoadFixtures
$entry->setName($this->getRandomProject());
$entry->setBudget(rand(1000, 100000));
$entry->setComment($this->getRandomPhrase());
$entry->setCustomerId(rand(1, self::AMOUNT_CUSTOMER)); // TODO
$entry->setCustomerId(rand(1, self::AMOUNT_CUSTOMER)); // TODO should be a user object
$entry->setVisible($i % 3 != 0);
$manager->persist($entry);

View File

@@ -27,11 +27,11 @@ class Customer
/**
* @var integer
*
* @ORM\Column(name="customerID", type="integer")
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $customerid;
private $id;
/**
* @var string
@@ -52,14 +52,7 @@ class Customer
*
* @ORM\Column(name="visible", type="boolean", nullable=false)
*/
private $visible = '1';
/**
* @var boolean
*
* @ORM\Column(name="filter", type="boolean", nullable=false)
*/
private $filter = '0';
private $visible = true;
/**
* @var string
@@ -153,11 +146,12 @@ class Customer
private $timezone;
/**
* @var boolean
*
* @ORM\Column(name="trash", type="boolean", nullable=false)
* @return int
*/
private $trash = '0';
public function getId()
{
return $this->id;
}
/**
* Set name
@@ -183,78 +177,6 @@ class Customer
return $this->name;
}
/**
* Set password
*
* @param string $password
*
* @return Customer
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set passwordresethash
*
* @param string $passwordresethash
*
* @return Customer
*/
public function setPasswordresethash($passwordresethash)
{
$this->passwordresethash = $passwordresethash;
return $this;
}
/**
* Get passwordresethash
*
* @return string
*/
public function getPasswordresethash()
{
return $this->passwordresethash;
}
/**
* Set secure
*
* @param string $secure
*
* @return Customer
*/
public function setSecure($secure)
{
$this->secure = $secure;
return $this;
}
/**
* Get secure
*
* @return string
*/
public function getSecure()
{
return $this->secure;
}
/**
* Set comment
*
@@ -303,30 +225,6 @@ class Customer
return $this->visible;
}
/**
* Set filter
*
* @param boolean $filter
*
* @return Customer
*/
public function setFilter($filter)
{
$this->filter = $filter;
return $this;
}
/**
* Get filter
*
* @return boolean
*/
public function getFilter()
{
return $this->filter;
}
/**
* Set company
*
@@ -638,38 +536,4 @@ class Customer
{
return $this->timezone;
}
/**
* Set trash
*
* @param boolean $trash
*
* @return Customer
*/
public function setTrash($trash)
{
$this->trash = $trash;
return $this;
}
/**
* Get trash
*
* @return boolean
*/
public function getTrash()
{
return $this->trash;
}
/**
* Get customerid
*
* @return integer
*/
public function getCustomerid()
{
return $this->customerid;
}
}

View File

@@ -34,7 +34,7 @@ class Project
private $id;
/**
* FIXME
* FIXME manytoone
* @var integer
*
* @ORM\Column(name="customerID", type="integer", nullable=false)
@@ -76,6 +76,16 @@ class Project
*/
private $activities;
/**
* Get projectid
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set customerid
*
@@ -196,16 +206,6 @@ class Project
return $this->budget;
}
/**
* Get projectid
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return Activity[]
*/

View File

@@ -18,38 +18,48 @@ use Doctrine\ORM\Mapping as ORM;
* Timesheet entity.
*
* @ORM\Entity(repositoryClass="TimesheetBundle\Repository\TimesheetRepository")
* @ORM\Table(name="timeSheet", indexes={@ORM\Index(columns={"userID"}), @ORM\Index(name="activity", columns={"activity"})})
* @ORM\Table(name="timesheet", indexes={@ORM\Index(columns={"user"}), @ORM\Index(name="activity", columns={"activity"})})
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class Timesheet
{
/**
* @var integer
*
* @ORM\Column(name="start", type="integer", nullable=false)
*/
private $start = '0';
/**
* @var integer
*
* @ORM\Column(name="end", type="integer", nullable=false)
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $end = '0';
private $id;
/**
* @var \DateTime
*
* @ORM\Column(name="start_time", type="datetime", nullable=false)
*/
private $begin;
/**
* @var \DateTime
*
* @ORM\Column(name="end_time", type="datetime", nullable=true)
*/
private $end;
/**
* @var integer
*
* @ORM\Column(name="duration", type="integer", nullable=false)
* @ORM\Column(name="duration", type="integer", nullable=true)
*/
private $duration = '0';
private $duration = 0;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* @ORM\JoinColumn(name="userID", referencedColumnName="userID")
* @ORM\JoinColumn(name="user", referencedColumnName="id")
*/
private $user;
@@ -68,142 +78,59 @@ class Timesheet
*/
private $description;
/**
* @var string
*
* @ORM\Column(name="comment", type="text", length=65535, nullable=true)
*/
private $comment;
/**
* @var boolean
*
* @ORM\Column(name="commentType", type="boolean", nullable=false)
*/
private $commenttype = '0';
/**
* @var boolean
*
* @ORM\Column(name="cleared", type="boolean", nullable=false)
*/
private $cleared = '0';
/**
* @var string
*
* @ORM\Column(name="location", type="string", length=50, nullable=true)
*/
private $location;
/**
* @var string
*
* @ORM\Column(name="trackingNumber", type="string", length=30, nullable=true)
*/
private $trackingnumber;
/**
* @var string
*
* @ORM\Column(name="rate", type="decimal", precision=10, scale=2, nullable=false)
*/
private $rate = '0.00';
private $rate = 0.00;
/**
* @var string
* Get entry id
*
* @ORM\Column(name="fixedRate", type="decimal", precision=10, scale=2, nullable=false)
* @return integer
*/
private $fixedrate = '0.00';
public function getId()
{
return $this->id;
}
/**
* @var string
*
* @ORM\Column(name="budget", type="decimal", precision=10, scale=2, nullable=true)
* @return \DateTime
*/
private $budget;
public function getBegin()
{
return $this->begin;
}
/**
* @var string
*
* @ORM\Column(name="approved", type="decimal", precision=10, scale=2, nullable=true)
*/
private $approved;
/**
* @var integer
*
* @ORM\Column(name="statusID", type="smallint", nullable=false)
*/
private $statusid;
/**
* @var boolean
*
* @ORM\Column(name="billable", type="boolean", nullable=true)
*/
private $billable;
/**
* @var integer
*
* @ORM\Column(name="timeEntryID", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $timeentryid;
/**
* Set start
*
* @param integer $start
*
* @param \DateTime $begin
* @return Timesheet
*/
public function setStart($start)
public function setBegin($begin)
{
$this->start = $start;
$this->begin = $begin;
return $this;
}
/**
* Get start
*
* @return integer
* @return \DateTime
*/
public function getStart()
public function getEnd()
{
return $this->start;
return $this->end;
}
/**
* Set end
*
* @param integer $end
*
* @param \DateTime $end
* @return Timesheet
*/
public function setEnd($end)
{
$this->end = $end;
return $this;
}
/**
* Get end
*
* @return integer
*/
public function getEnd()
{
return $this->end;
}
/**
* Set duration
*
@@ -214,7 +141,6 @@ class Timesheet
public function setDuration($duration)
{
$this->duration = $duration;
return $this;
}
@@ -238,7 +164,6 @@ class Timesheet
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
@@ -262,7 +187,6 @@ class Timesheet
public function setActivity($activity)
{
$this->activity = $activity;
return $this;
}
@@ -286,7 +210,6 @@ class Timesheet
public function setDescription($description)
{
$this->description = $description;
return $this;
}
@@ -300,126 +223,6 @@ class Timesheet
return $this->description;
}
/**
* Set comment
*
* @param string $comment
*
* @return Timesheet
*/
public function setComment($comment)
{
$this->comment = $comment;
return $this;
}
/**
* Get comment
*
* @return string
*/
public function getComment()
{
return $this->comment;
}
/**
* Set commenttype
*
* @param boolean $commenttype
*
* @return Timesheet
*/
public function setCommenttype($commenttype)
{
$this->commenttype = $commenttype;
return $this;
}
/**
* Get commenttype
*
* @return boolean
*/
public function getCommenttype()
{
return $this->commenttype;
}
/**
* Set cleared
*
* @param boolean $cleared
*
* @return Timesheet
*/
public function setCleared($cleared)
{
$this->cleared = $cleared;
return $this;
}
/**
* Get cleared
*
* @return boolean
*/
public function getCleared()
{
return $this->cleared;
}
/**
* Set location
*
* @param string $location
*
* @return Timesheet
*/
public function setLocation($location)
{
$this->location = $location;
return $this;
}
/**
* Get location
*
* @return string
*/
public function getLocation()
{
return $this->location;
}
/**
* Set trackingnumber
*
* @param string $trackingnumber
*
* @return Timesheet
*/
public function setTrackingnumber($trackingnumber)
{
$this->trackingnumber = $trackingnumber;
return $this;
}
/**
* Get trackingnumber
*
* @return string
*/
public function getTrackingnumber()
{
return $this->trackingnumber;
}
/**
* Set rate
*
@@ -430,7 +233,6 @@ class Timesheet
public function setRate($rate)
{
$this->rate = $rate;
return $this;
}
@@ -443,134 +245,4 @@ class Timesheet
{
return $this->rate;
}
/**
* Set fixedrate
*
* @param string $fixedrate
*
* @return Timesheet
*/
public function setFixedrate($fixedrate)
{
$this->fixedrate = $fixedrate;
return $this;
}
/**
* Get fixedrate
*
* @return string
*/
public function getFixedrate()
{
return $this->fixedrate;
}
/**
* Set budget
*
* @param string $budget
*
* @return Timesheet
*/
public function setBudget($budget)
{
$this->budget = $budget;
return $this;
}
/**
* Get budget
*
* @return string
*/
public function getBudget()
{
return $this->budget;
}
/**
* Set approved
*
* @param string $approved
*
* @return Timesheet
*/
public function setApproved($approved)
{
$this->approved = $approved;
return $this;
}
/**
* Get approved
*
* @return string
*/
public function getApproved()
{
return $this->approved;
}
/**
* Set statusid
*
* @param integer $statusid
*
* @return Timesheet
*/
public function setStatusid($statusid)
{
$this->statusid = $statusid;
return $this;
}
/**
* Get statusid
*
* @return integer
*/
public function getStatusid()
{
return $this->statusid;
}
/**
* Set billable
*
* @param boolean $billable
*
* @return Timesheet
*/
public function setBillable($billable)
{
$this->billable = $billable;
return $this;
}
/**
* Get billable
*
* @return boolean
*/
public function getBillable()
{
return $this->billable;
}
/**
* Get timeentryid
*
* @return integer
*/
public function getTimeentryid()
{
return $this->timeentryid;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Model;
/**
* Activity statistics
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class ActivityStatistic
{
/**
* @var int
*/
protected $totalAmount = 0;
/**
* @return int
*/
public function getTotalAmount()
{
return $this->totalAmount;
}
/**
* @param int $totalAmount
*/
public function setTotalAmount($totalAmount)
{
$this->totalAmount = $totalAmount;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Model;
/**
* Project statistics
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class ProjectStatistic
{
/**
* @var int
*/
protected $totalAmount = 0;
/**
* @return int
*/
public function getTotalAmount()
{
return $this->totalAmount;
}
/**
* @param int $totalAmount
*/
public function setTotalAmount($totalAmount)
{
$this->totalAmount = $totalAmount;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Model;
/**
* Timesheet statistics
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class TimesheetGlobalStatistic extends TimesheetStatistic
{
/**
* @var int
*/
protected $activeThisMonth = 0;
/**
* @var int
*/
protected $activeTotal = 0;
/**
* @var int
*/
protected $activeCurrently = 0;
/**
* @return int
*/
public function getActiveCurrently()
{
return $this->activeCurrently;
}
/**
* @param int $activeCurrently
*/
public function setActiveCurrently($activeCurrently)
{
$this->activeCurrently = $activeCurrently;
}
/**
* @return int
*/
public function getActiveThisMonth()
{
return $this->activeThisMonth;
}
/**
* @param int $activeThisMonth
*/
public function setActiveThisMonth($activeThisMonth)
{
$this->activeThisMonth = $activeThisMonth;
}
/**
* @return int
*/
public function getActiveTotal()
{
return $this->activeTotal;
}
/**
* @param int $activeTotal
*/
public function setActiveTotal($activeTotal)
{
$this->activeTotal = $activeTotal;
}
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Model;
/**
* Timesheet statistics
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class TimesheetStatistic
{
/**
* @var int
*/
protected $durationThisMonth = 0;
/**
* @var int
*/
protected $durationTotal = 0;
/**
* @var int
*/
protected $amountThisMonth = 0;
/**
* @var int
*/
protected $amountTotal = 0;
/**
* @return int
*/
public function getDurationThisMonth()
{
return $this->durationThisMonth;
}
/**
* @param int $durationThisMonth
*/
public function setDurationThisMonth($durationThisMonth)
{
$this->durationThisMonth = $durationThisMonth;
}
/**
* @return int
*/
public function getAmountTotal()
{
return $this->amountTotal;
}
/**
* @param int $amountTotal
*/
public function setAmountTotal($amountTotal)
{
$this->amountTotal = $amountTotal;
}
/**
* @return int
*/
public function getDurationTotal()
{
return $this->durationTotal;
}
/**
* @param int $durationTotal
*/
public function setDurationTotal($durationTotal)
{
$this->durationTotal = $durationTotal;
}
/**
* @return int
*/
public function getAmountThisMonth()
{
return $this->amountThisMonth;
}
/**
* @param int $amountThisMonth
*/
public function setAmountThisMonth($amountThisMonth)
{
$this->amountThisMonth = $amountThisMonth;
}
}

View File

@@ -17,6 +17,7 @@ use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use TimesheetBundle\Model\ActivityStatistic;
/**
* Class ActivityRepository
@@ -26,17 +27,48 @@ use Pagerfanta\Pagerfanta;
class ActivityRepository extends EntityRepository
{
/**
* Return statistic data for all user.
*
* @return ActivityStatistic
*/
public function getGlobalStatistics()
{
$countAll = $this->getEntityManager()
->createQuery('SELECT COUNT(a.id) FROM TimesheetBundle:Activity a')
->getSingleScalarResult();
$stats = new ActivityStatistic();
$stats->setTotalAmount($countAll);
return $stats;
}
/**
* @param User $user
* @return Query
*/
public function queryLatest(User $user = null)
protected function queryLatest(User $user = null)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('a')
->from('TimesheetBundle:Activity', 'a')
->orderBy('a.name', 'DESC');
->orderBy('a.id', 'DESC');
return $qb->getQuery();
}
/**
* @param string $orderBy
* @return Query
*/
protected function queryAll($orderBy = 'id')
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('a')
->from('TimesheetBundle:Activity', 'a')
->orderBy('a.' . $orderBy, 'ASC');
return $qb->getQuery();
}

View File

@@ -17,6 +17,7 @@ use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use TimesheetBundle\Model\ProjectStatistic;
/**
* Class ProjectRepository
@@ -26,11 +27,27 @@ use Pagerfanta\Pagerfanta;
class ProjectRepository extends EntityRepository
{
/**
* Return statistic data for all user.
*
* @return ProjectStatistic
*/
public function getGlobalStatistics()
{
$countAll = $this->getEntityManager()
->createQuery('SELECT COUNT(p.id) FROM TimesheetBundle:Project p')
->getSingleScalarResult();
$stats = new ProjectStatistic();
$stats->setTotalAmount($countAll);
return $stats;
}
/**
* @param User $user
* @return Query
*/
public function queryLatest(User $user = null)
protected function queryLatest(User $user = null)
{
$qb = $this->getEntityManager()->createQueryBuilder();
@@ -41,7 +58,11 @@ class ProjectRepository extends EntityRepository
return $qb->getQuery();
}
public function queryAll($orderBy = 'id')
/**
* @param string $orderBy
* @return Query
*/
protected function queryAll($orderBy = 'id')
{
$qb = $this->getEntityManager()->createQueryBuilder();

View File

@@ -15,8 +15,12 @@ use AppBundle\Entity\User;
use TimesheetBundle\Entity\Timesheet;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\DBAL\Types\Type;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use TimesheetBundle\Model\TimesheetGlobalStatistic;
use TimesheetBundle\Model\TimesheetStatistic;
use DateTime;
/**
* Class TimesheetRepository
@@ -26,6 +30,122 @@ use Pagerfanta\Pagerfanta;
class TimesheetRepository extends EntityRepository
{
protected function queryThisMonth($select, User $user = null)
{
$qb = $this->getEntityManager()->createQueryBuilder();
// FIXME
$end = new DateTime();
$begin = $end->sub(new \DateInterval("P30D"));
$qb->select($select)
->from('TimesheetBundle:Timesheet', 't')
->where($qb->expr()->gt('t.begin', ':from'))
->andWhere($qb->expr()->gt('t.end', ':to'))
->setParameter('from', $begin, Type::DATETIME)
->setParameter('to', $end, Type::DATETIME);
if (null !== $user) {
$qb->andWhere('t.user = :user')
->setParameter('user', $user);
}
return $qb;
}
/**
* Fetch statistic data for one user.
*
* @param User $user
* @return TimesheetStatistic
*/
public function getUserStatistics(User $user)
{
$durationTotal = $this->getEntityManager()
->createQuery('SELECT SUM(t.duration) FROM TimesheetBundle:Timesheet t WHERE t.user = :user')
->setParameter('user', $user)
->getSingleScalarResult();
$rateTotal = $this->getEntityManager()
->createQuery('SELECT SUM(t.rate) FROM TimesheetBundle:Timesheet t WHERE t.user = :user')
->setParameter('user', $user)
->getSingleScalarResult();
$amountMonth = $this->queryThisMonth('SUM(t.rate)', $user)
->getQuery()
->getSingleScalarResult();
$durationMonth = $this->queryThisMonth('SUM(t.duration)', $user)
->getQuery()
->getSingleScalarResult();
$stats = new TimesheetStatistic();
$stats->setAmountTotal($rateTotal);
$stats->setDurationTotal($durationTotal);
$stats->setAmountThisMonth($amountMonth);
$stats->setDurationThisMonth($durationMonth);
return $stats;
}
/**
* Fetch statistic data for all user.
*
* @return TimesheetStatistic
*/
public function getGlobalStatistics()
{
$durationTotal = $this->getEntityManager()
->createQuery('SELECT SUM(t.duration) FROM TimesheetBundle:Timesheet t')
->getSingleScalarResult();
$rateTotal = $this->getEntityManager()
->createQuery('SELECT SUM(t.rate) FROM TimesheetBundle:Timesheet t')
->getSingleScalarResult();
$userTotal = $this->getEntityManager()
->createQuery('SELECT COUNT(DISTINCT(t.user)) FROM TimesheetBundle:Timesheet t')
->getSingleScalarResult();
$activeNow = $this->getActiveEntry();
$amountMonth = $this->queryThisMonth('SUM(t.rate)')
->getQuery()
->getSingleScalarResult();
$durationMonth = $this->queryThisMonth('SUM(t.duration)')
->getQuery()
->getSingleScalarResult();
$activeMonth = $this->queryThisMonth('COUNT(DISTINCT(t.user))')
->getQuery()
->getSingleScalarResult();
$stats = new TimesheetGlobalStatistic();
$stats->setAmountTotal($rateTotal);
$stats->setDurationTotal($durationTotal);
$stats->setActiveTotal($userTotal);
$stats->setActiveCurrently(count($activeNow));
$stats->setActiveThisMonth($activeMonth);
$stats->setAmountThisMonth($amountMonth);
$stats->setDurationThisMonth($durationMonth);
return $stats;
}
/**
* @param User $user
* @return Query
*/
public function getActiveEntry(User $user = null)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('t')
->from('TimesheetBundle:Timesheet', 't')
->where('t.begin > 0')
->andWhere('t.end is NULL');
$params = [];
if (null !== $user) {
$qb->andWhere('t.user = :user');
$params['user'] = $user;
}
return $qb->getQuery()->execute($params);
}
/**
* @param User $user
* @return Query
@@ -36,7 +156,7 @@ class TimesheetRepository extends EntityRepository
$qb->select('t')
->from('TimesheetBundle:Timesheet', 't')
->orderBy('t.start', 'DESC');
->orderBy('t.begin', 'DESC');
if (null !== $user) {
$qb->where('t.user = :user')

View File

@@ -0,0 +1,12 @@
{% import "AvanzuAdminThemeBundle:layout:macros.html.twig" as macro %}
<!-- Sidebar user panel -->
<div class="user-panel">
<div class="pull-left image">
<a href="#" id="ticktac">
<img src="{{ asset('images/buzzer-on.png') }}"/>
</a>
</div>
<div class="pull-left info">
<p>10:39:05</p>
</div>
</div>

View File

@@ -1,37 +1,31 @@
{% extends 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as tables %}
{% block page_title %}{{ 'admin_activity.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_activity.subtitle'|trans }}{% endblock %}
{% block main %}
{% if entries.count > 0 %}
<table class="table table-striped">
<thead>
<tr>
<th>{{ 'label.id'|trans }}</th>
<th><i class="fa fa-bookmark-o"></i> {{ 'label.name'|trans }}</th>
<th><i class="fa fa-book"></i> {{ 'label.project'|trans }}</th>
<th><i class="fa fa-comment-o"></i> {{ 'label.comment'|trans }}</th>
<th><i class="fa fa-eye"></i> {{ 'label.visible'|trans }}</th>
</tr>
</thead>
<tbody>
{% for entry in entries %}
<tr>
<td>{{ entry.id }}</td>
<td>{{ entry.name }}</td>
<td>{{ entry.project }}</td>
<td>{{ entry.comment }}</td>
<td>{{ widgets.badge_visible(entry.visible) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ tables.data_table_header({
'label.id': '',
'label.name': 'bookmark-o',
'label.project': 'book',
'label.comment': 'comment-o',
'label.visible': 'eye',
}) }}
<div class="navigation text-center">
{{ pagerfanta(entries, 'twitter_bootstrap3_translated', { routeName: 'admin_activity_paginated' }) }}
</div>
{% for entry in entries %}
<tr>
<td>{{ entry.id }}</td>
<td>{{ entry.name }}</td>
<td>{{ entry.project }}</td>
<td>{{ entry.comment }}</td>
<td>{{ widgets.label_visible(entry.visible) }}</td>
</tr>
{% endfor %}
{{ tables.data_table_footer(entries, 'admin_activity_paginated') }}
{% else %}
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}

View File

@@ -1,39 +1,33 @@
{% extends 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as tables %}
{% block page_title %}{{ 'admin_project.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_project.subtitle'|trans }}{% endblock %}
{% block main %}
{% if entries.count > 0 %}
<table class="table table-striped">
<thead>
<tr>
<th>{{ 'label.id'|trans }}</th>
<th><i class="fa fa-bookmark-o"></i> {{ 'label.name'|trans }}</th>
<th><i class="fa fa-comment-o"></i> {{ 'label.comment'|trans }}</th>
<th><i class="fa fa-eye"></i> {{ 'label.visible'|trans }}</th>
<th><i class="fa fa-tasks"></i> {{ 'label.activity'|trans }}</th>
<th><i class="fa fa-credit-card"></i> {{ 'label.budget'|trans }}</th>
</tr>
</thead>
<tbody>
{% for entry in entries %}
<tr>
<td>{{ entry.id }}</td>
<td>{{ entry.name }}</td>
<td>{{ entry.comment }}</td>
<td>{{ widgets.badge_visible(entry.visible) }}</td>
<td>{{ widgets.badge_counter(entry.activities.count) }}</td>
<td>{{ entry.budget}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ tables.data_table_header({
'label.id': '',
'label.name': 'bookmark-o',
'label.comment': 'comment-o',
'label.activity': 'tasks',
'label.budget': 'credit-card',
'label.visible': 'eye',
}) }}
<div class="navigation text-center">
{{ pagerfanta(entries, 'twitter_bootstrap3_translated', { routeName: 'admin_project_paginated' }) }}
</div>
{% for entry in entries %}
<tr>
<td>{{ entry.id }}</td>
<td>{{ entry.name }}</td>
<td>{{ entry.comment }}</td>
<td>{{ widgets.badge_counter(entry.activities.count) }}</td>
<td>{{ entry.budget|money}}</td>
<td>{{ widgets.label_visible(entry.visible) }}</td>
</tr>
{% endfor %}
{{ tables.data_table_footer(entries, 'admin_project_paginated') }}
{% else %}
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}

View File

@@ -1,39 +1,32 @@
{% extends 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as widgets %}
{% block page_title %}{{ 'admin_timesheet.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'admin_timesheet.subtitle'|trans }}{% endblock %}
{% block main %}
{% if entries.count > 0 %}
<table class="table table-striped">
<thead>
<tr>
<th><i class="fa fa-calendar"></i> {{ 'label.date'|trans }}</th>
<th><i class="fa fa-hourglass-start"></i> {{ 'label.starttime'|trans }}</th>
<th><i class="fa fa-hourglass-end"></i> {{ 'label.endtime'|trans }}</th>
<th><i class="fa fa-clock-o"></i> {{ 'label.duration'|trans }}</th>
<th><i class="fa fa-user"></i> {{ 'label.username'|trans }}</th>
<th><i class="fa fa-pencil-square-o"></i> {{ 'label.description'|trans }}</th>
</tr>
</thead>
<tbody>
{% for entry in entries %}
<tr>
<td>{{ entry.start|date(kimai_context.date_1) }}</td>
<td>{{ entry.start|date("H:i") }}</td>
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|gmdate }}</td>
<td>{{ entry.user.username }}</td>
<td>{{ entry.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ widgets.data_table_header({
'label.date': 'calendar',
'label.starttime': 'hourglass-start',
'label.endtime': 'hourglass-end',
'label.duration': 'clock-o',
'label.username': 'user',
'label.description': 'pencil-square-o',
}) }}
<div class="navigation text-center">
{{ pagerfanta(entries, 'twitter_bootstrap3_translated', { routeName: 'admin_timesheet_paginated' }) }}
</div>
{% for entry in entries %}
<tr>
<td>{{ entry.begin|date(kimai_context.date_1) }}</td>
<td>{{ entry.begin|date("H:i") }}</td>
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|duration }}</td>
<td>{{ entry.user.username }}</td>
<td>{{ entry.description }}</td>
</tr>
{% endfor %}
{{ widgets.data_table_footer(entries, 'admin_timesheet_paginated') }}
{% else %}
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}

View File

@@ -1,39 +1,30 @@
{% extends 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as widgets %}
{% block page_title %}{{ 'timesheet.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'timesheet.subtitle'|trans }}{% endblock %}
{% block main %}
{% if entries.count > 0 %}
<table class="table table-striped">
<thead>
<tr>
<th><i class="fa fa-calendar"></i> {{ 'label.date'|trans }}</th>
<th><i class="fa fa-hourglass-start"></i> {{ 'label.starttime'|trans }}</th>
<th><i class="fa fa-hourglass-end"></i> {{ 'label.endtime'|trans }}</th>
<th><i class="fa fa-clock-o"></i> {{ 'label.duration'|trans }}</th>
<th><i class="fa fa-user"></i> {{ 'label.username'|trans }}</th>
<th><i class="fa fa-pencil-square-o"></i> {{ 'label.description'|trans }}</th>
</tr>
</thead>
<tbody>
{% for entry in entries %}
<tr>
<td>{{ entry.start|date(kimai_context.date_1) }}</td>
<td>{{ entry.start|date("H:i") }}</td>
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|gmdate }}</td>
<td>{{ entry.user.username }}</td>
<td>{{ entry.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ widgets.data_table_header({
'label.date': 'calendar',
'label.starttime': 'hourglass-start',
'label.endtime': 'hourglass-end',
'label.duration': 'clock-o',
'label.description': 'pencil-square-o',
}) }}
<div class="navigation text-center">
{{ pagerfanta(entries, 'twitter_bootstrap3_translated', { routeName: 'timesheet_paginated' }) }}
</div>
{% for entry in entries %}
<tr>
<td>{{ entry.begin|date(kimai_context.date_1) }}</td>
<td>{{ entry.begin|date("H:i") }}</td>
<td>{{ entry.end|date("H:i") }}</td>
<td>{{ entry.duration|duration }}</td>
<td>{{ entry.description }}</td>
</tr>
{% endfor %}
{{ widgets.data_table_footer(entries, 'timesheet_paginated') }}
{% else %}
{{ widgets.callout('warning', 'error.no_entries_found') }}
{% endif %}