javascript and api to stop and display active records (#772)
This commit is contained in:
@@ -400,7 +400,7 @@ class TimesheetController extends BaseApiController
|
||||
*
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Returns the collection of recent user activities (always the latest entry of a unique working set groued by customer, project and activity)",
|
||||
* description="Returns the collection of recent user activities (always the latest entry of a unique working set grouped by customer, project and activity)",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/TimesheetSubCollection")
|
||||
@@ -443,4 +443,75 @@ class TimesheetController extends BaseApiController
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collection of active timesheet records
|
||||
*
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Returns the collection of active timesheet records for the current user",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/TimesheetSubCollection")
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('view_own_timesheet')")
|
||||
* @return Response
|
||||
* @throws \Doctrine\ORM\Query\QueryException
|
||||
*/
|
||||
public function activeAction()
|
||||
{
|
||||
$data = $this->repository->getActiveEntries($this->getUser());
|
||||
|
||||
$view = new View($data, 200);
|
||||
$view->getContext()->setGroups(['Default', 'Subresource', 'Timesheet']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops an active timesheet record
|
||||
*
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Stops an active timesheet record and returns it afterwards.",
|
||||
* @SWG\Schema(ref="#/definitions/TimesheetEntity")
|
||||
* )
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* type="integer",
|
||||
* description="Timesheet record ID to stop",
|
||||
* required=true,
|
||||
* )
|
||||
*
|
||||
* @Security("is_granted('stop_own_timesheet') or is_granted('stop_other_timesheet')")
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws \App\Repository\RepositoryException
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function stopAction($id)
|
||||
{
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $this->repository->find($id);
|
||||
|
||||
if (null === $timesheet) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!$this->isGranted('stop', $timesheet)) {
|
||||
throw new AccessDeniedHttpException('You are not allowed to stop this timesheet');
|
||||
}
|
||||
|
||||
$this->repository->stopRecording($timesheet);
|
||||
|
||||
$view = new View($timesheet, 200);
|
||||
$view->getContext()->setGroups(['Default', 'Entity', 'Timesheet']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ trait StringAccessibleConfigTrait
|
||||
$temp = array_slice($temp, 1);
|
||||
}
|
||||
foreach ($temp as $key2) {
|
||||
if (!isset($array[$key2])) {
|
||||
if (!array_key_exists($key2, $array)) {
|
||||
// unknown values will silently be skipped
|
||||
continue 2;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ trait StringAccessibleConfigTrait
|
||||
$keys = explode('.', $key);
|
||||
$search = array_shift($keys);
|
||||
|
||||
if (!isset($config[$search])) {
|
||||
if (!array_key_exists($search, $config)) {
|
||||
throw new \InvalidArgumentException('Unknown config: ' . $key);
|
||||
}
|
||||
|
||||
|
||||
25
src/Configuration/ThemeConfiguration.php
Normal file
25
src/Configuration/ThemeConfiguration.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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\Configuration;
|
||||
|
||||
class ThemeConfiguration implements SystemBundleConfiguration
|
||||
{
|
||||
use StringAccessibleConfigTrait;
|
||||
|
||||
public function getPrefix(): string
|
||||
{
|
||||
return 'theme';
|
||||
}
|
||||
|
||||
public function getSelectPicker(): string
|
||||
{
|
||||
return (string) $this->find('select_type');
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,13 @@ class ActivityController extends AbstractController
|
||||
{
|
||||
$stats = $this->getRepository()->getActivityStatistics($activity);
|
||||
|
||||
$deleteForm = $this->createFormBuilder()
|
||||
$deleteForm = $this->createFormBuilder(null, [
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.activityUpdate kimai.activityDelete',
|
||||
'data-msg-success' => 'action.delete.success',
|
||||
'data-msg-error' => 'action.delete.error',
|
||||
]
|
||||
])
|
||||
->add('activity', ActivityType::class, [
|
||||
'label' => 'label.activity',
|
||||
'query_builder' => function (ActivityRepository $repo) use ($activity) {
|
||||
|
||||
@@ -134,7 +134,13 @@ class CustomerController extends AbstractController
|
||||
{
|
||||
$stats = $this->getRepository()->getCustomerStatistics($customer);
|
||||
|
||||
$deleteForm = $this->createFormBuilder()
|
||||
$deleteForm = $this->createFormBuilder(null, [
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.customerUpdate kimai.customerDelete',
|
||||
'data-msg-success' => 'action.delete.success',
|
||||
'data-msg-error' => 'action.delete.error',
|
||||
]
|
||||
])
|
||||
->add('customer', CustomerType::class, [
|
||||
'label' => 'label.customer',
|
||||
'query_builder' => function (CustomerRepository $repo) use ($customer) {
|
||||
|
||||
@@ -119,7 +119,13 @@ class ProjectController extends AbstractController
|
||||
{
|
||||
$stats = $this->getRepository()->getProjectStatistics($project);
|
||||
|
||||
$deleteForm = $this->createFormBuilder()
|
||||
$deleteForm = $this->createFormBuilder(null, [
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.projectUpdate kimai.projectDelete',
|
||||
'data-msg-success' => 'action.delete.success',
|
||||
'data-msg-error' => 'action.delete.error',
|
||||
]
|
||||
])
|
||||
->add('project', ProjectType::class, [
|
||||
'label' => 'label.project',
|
||||
'query_builder' => function (ProjectRepository $repo) use ($project) {
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Event\SystemConfigurationEvent;
|
||||
use App\Form\Model\Configuration;
|
||||
use App\Form\Model\SystemConfiguration as SystemConfigurationModel;
|
||||
use App\Form\SystemConfigurationForm;
|
||||
use App\Form\Type\EnhancedSelectboxType;
|
||||
use App\Form\Type\TimesheetModeType;
|
||||
use App\Repository\ConfigurationRepository;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
@@ -82,6 +83,16 @@ class SystemConfigurationController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/theme", name="system_configuration_theme", methods={"POST"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function theme(Request $request)
|
||||
{
|
||||
return $this->handleConfigUpdate($request, SystemConfigurationModel::SECTION_THEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/timesheet", name="system_configuration_timesheet", methods={"POST"})
|
||||
*
|
||||
@@ -244,6 +255,15 @@ class SystemConfigurationController extends AbstractController
|
||||
->setLabel('currency')
|
||||
->setType(CurrencyType::class),
|
||||
]),
|
||||
(new SystemConfigurationModel())
|
||||
->setSection(SystemConfigurationModel::SECTION_THEME)
|
||||
->setConfiguration([
|
||||
(new Configuration())
|
||||
->setName('theme.select_type')
|
||||
->setLabel('theme.select_type')
|
||||
->setTranslationDomain('system-configuration')
|
||||
->setType(EnhancedSelectboxType::class),
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,20 +133,6 @@ class TimesheetController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The route to stop a running entry.
|
||||
*
|
||||
* @Route(path="/{id}/stop", name="timesheet_stop", methods={"GET"})
|
||||
* @Security("is_granted('stop', entry)")
|
||||
*
|
||||
* @param Timesheet $entry
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function stopAction(Timesheet $entry)
|
||||
{
|
||||
return $this->stop($entry, 'timesheet');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/start/{id}", name="timesheet_start", requirements={"id" = "\d+"}, methods={"GET", "POST"})
|
||||
* @Security("is_granted('start', timesheet)")
|
||||
@@ -233,10 +219,17 @@ class TimesheetController extends AbstractController
|
||||
* @param Timesheet $entry
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deleteAction(Timesheet $entry, Request $request)
|
||||
{
|
||||
$deleteForm = $this->createFormBuilder()
|
||||
$deleteForm = $this->createFormBuilder(null, [
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.timesheetUpdate kimai.timesheetDelete',
|
||||
'data-msg-success' => 'action.delete.success',
|
||||
'data-msg-error' => 'action.delete.error',
|
||||
],
|
||||
])
|
||||
->setAction($this->generateUrl('timesheet_delete', ['id' => $entry->getId()]))
|
||||
->setMethod('POST')
|
||||
->getForm();
|
||||
|
||||
@@ -69,23 +69,6 @@ trait TimesheetControllerTrait
|
||||
return $this->getDoctrine()->getRepository(Timesheet::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $entry
|
||||
* @param string $route
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
protected function stop(Timesheet $entry, $route)
|
||||
{
|
||||
try {
|
||||
$this->getRepository()->stopRecording($entry);
|
||||
$this->flashSuccess('timesheet.stop.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('timesheet.stop.error', ['%reason%' => $ex->getMessage()]);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute($route);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $entry
|
||||
* @param Request $request
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Form\DeleteType;
|
||||
use App\Form\TimesheetEditForm;
|
||||
use App\Form\Toolbar\TimesheetToolbarForm;
|
||||
use App\Repository\ActivityRepository;
|
||||
@@ -108,18 +109,6 @@ class TimesheetTeamController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/{id}/stop", name="admin_timesheet_stop", methods={"GET"})
|
||||
* @Security("is_granted('stop', entry)")
|
||||
*
|
||||
* @param Timesheet $entry
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function stopAction(Timesheet $entry)
|
||||
{
|
||||
return $this->stop($entry, 'admin_timesheet');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/{id}/edit", name="admin_timesheet_edit", methods={"GET", "POST"})
|
||||
* @Security("is_granted('edit', entry)")
|
||||
@@ -157,7 +146,13 @@ class TimesheetTeamController extends AbstractController
|
||||
*/
|
||||
public function deleteAction(Timesheet $entry, Request $request)
|
||||
{
|
||||
$deleteForm = $this->createFormBuilder()
|
||||
$deleteForm = $this->createFormBuilder(null, [
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.timesheetUpdate kimai.timesheetDelete',
|
||||
'data-msg-success' => 'action.delete.success',
|
||||
'data-msg-error' => 'action.delete.error',
|
||||
]
|
||||
])
|
||||
->setAction($this->generateUrl('admin_timesheet_delete', ['id' => $entry->getId()]))
|
||||
->setMethod('POST')
|
||||
->getForm();
|
||||
|
||||
@@ -140,7 +140,13 @@ class UserController extends AbstractController
|
||||
// $userToDelete MUST not be called $user, as $user is always the current user!
|
||||
$stats = $this->getDoctrine()->getRepository(Timesheet::class)->getUserStatistics($userToDelete);
|
||||
|
||||
$deleteForm = $this->createFormBuilder()
|
||||
$deleteForm = $this->createFormBuilder(null, [
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.userUpdate kimai.userDelete',
|
||||
'data-msg-success' => 'action.delete.success',
|
||||
'data-msg-error' => 'action.delete.error',
|
||||
]
|
||||
])
|
||||
->setAction($this->generateUrl('admin_user_delete', ['id' => $userToDelete->getId()]))
|
||||
->setMethod('POST')
|
||||
->getForm();
|
||||
|
||||
@@ -295,6 +295,7 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->scalarNode('box_color')
|
||||
->defaultValue('green')
|
||||
->setDeprecated('The node "%node%" at path "%path%" was removed, please delete it from your config.')
|
||||
->end()
|
||||
->scalarNode('select_type')
|
||||
->defaultNull()
|
||||
|
||||
@@ -103,6 +103,7 @@ class DashboardSubscriber implements EventSubscriberInterface
|
||||
$widget
|
||||
->setRoute('admin_user')
|
||||
->setIcon('user')
|
||||
->setColor('green')
|
||||
->setType(Widget::TYPE_MORE)
|
||||
;
|
||||
$section->addWidget($widget);
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
|
||||
namespace App\Form\Extension;
|
||||
|
||||
use App\Configuration\ThemeConfiguration;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
|
||||
@@ -29,14 +31,14 @@ class EnhancedChoiceTypeExtension extends AbstractTypeExtension
|
||||
/**
|
||||
* @param null|string $type
|
||||
*/
|
||||
public function __construct(?string $type)
|
||||
public function __construct(ThemeConfiguration $configuration)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->type = $configuration->getSelectPicker();
|
||||
}
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [EntityType::class];
|
||||
return [EntityType::class, ChoiceType::class];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ class SystemConfiguration
|
||||
{
|
||||
public const SECTION_TIMESHEET = 'timesheet';
|
||||
public const SECTION_FORM_CUSTOMER = 'form_customer';
|
||||
public const SECTION_THEME = 'theme';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
||||
@@ -291,6 +291,11 @@ class TimesheetEditForm extends AbstractType
|
||||
'method' => 'POST',
|
||||
'date_format' => null,
|
||||
'customer' => false,
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.timesheetUpdate',
|
||||
'data-msg-success' => 'action.update.success',
|
||||
'data-msg-error' => 'action.update.error',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
43
src/Form/Type/EnhancedSelectboxType.php
Normal file
43
src/Form/Type/EnhancedSelectboxType.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to select the javascript renderer for select boxes.
|
||||
*/
|
||||
class EnhancedSelectboxType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.select_type',
|
||||
'choices' => [
|
||||
'' => null,
|
||||
'Javascript' => 'selectpicker',
|
||||
],
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class TimesheetRepository extends AbstractRepository
|
||||
|
||||
$entityManager = $this->getEntityManager();
|
||||
$entityManager->persist($entry);
|
||||
$entityManager->flush();
|
||||
$entityManager->flush($entry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user