javascript and api to stop and display active records (#772)

This commit is contained in:
Kevin Papst
2019-05-10 13:45:09 +02:00
committed by GitHub
parent e619b5fd31
commit 09da7cd242
86 changed files with 918 additions and 449 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}

View 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');
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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),
]),
];
}
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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()

View File

@@ -103,6 +103,7 @@ class DashboardSubscriber implements EventSubscriberInterface
$widget
->setRoute('admin_user')
->setIcon('user')
->setColor('green')
->setType(Widget::TYPE_MORE)
;
$section->addWidget($widget);

View File

@@ -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];
}
/**

View File

@@ -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

View File

@@ -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',
],
]);
}
}

View 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;
}
}

View File

@@ -76,7 +76,7 @@ class TimesheetRepository extends AbstractRepository
$entityManager = $this->getEntityManager();
$entityManager->persist($entry);
$entityManager->flush();
$entityManager->flush($entry);
return true;
}