form improvement

using theme clone
This commit is contained in:
Kevin Papst
2016-11-13 22:19:18 +01:00
parent cc269d3142
commit aaa2fffa08
22 changed files with 411 additions and 60 deletions

View File

@@ -30,6 +30,8 @@ class YesNoType extends AbstractType
{
$resolver->setDefaults([
'choices' => ['yes' => true, 'no' => false],
'multiple' => false,
'expanded' => true,
]);
}

View File

@@ -82,22 +82,23 @@ class Extensions extends \Twig_Extension
$minute = $minute > 9 ? $minute : '0' . $minute;
if (!$includeSeconds) {
return $hour . ':' . $minute;
return $hour . ':' . $minute . ' h';
}
$second = $seconds % 60;
$second = $second > 9 ? $second : '0' . $second;
return $hour . ':' . $minute . ':' . $second . 'h';
return $hour . ':' . $minute . ':' . $second . ' h';
}
/**
* @param float $amount
* @param string $currency
* @return string
*/
public function money($amount)
public function money($amount, $currency = 'EUR')
{
return round($amount) . ' ';
return round($amount) . ' ' . Intl::getCurrencyBundle()->getCurrencySymbol($currency);
}
/**

View File

@@ -12,13 +12,15 @@
namespace TimesheetBundle\Controller\Admin;
use Pagerfanta\Pagerfanta;
use Symfony\Component\HttpFoundation\Request;
use TimesheetBundle\Entity\Project;
use TimesheetBundle\Entity\Timesheet;
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;
use TimesheetBundle\Form\ProjectEditForm;
use TimesheetBundle\Repository\ProjectRepository;
/**
* Controller used to manage projects in the admin part of the site.
@@ -35,6 +37,9 @@ class ProjectController extends Controller
* @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_project_paginated")
* @Method("GET")
* @Cache(smaxage="10")
*
* @param $page
* @return \Symfony\Component\HttpFoundation\Response
*/
public function indexAction($page)
{
@@ -43,4 +48,72 @@ class ProjectController extends Controller
return $this->render('TimesheetBundle:admin:project.html.twig', ['entries' => $entries]);
}
/**
* @Route("/{id}/edit", name="admin_project_edit")
* @Method({"GET", "POST"})
*
* @param $id
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function editAction($id, Request $request)
{
$project = $this->getById($id);
$editForm = $this->createEditForm($project);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($project);
$entityManager->flush();
$this->addFlash('success', 'action.updated_successfully');
return $this->redirectToRoute(
'admin_project', ['id' => $project->getId()]
);
}
return $this->render(
'TimesheetBundle:admin:project_edit.html.twig',
[
'project' => $project,
'form' => $editForm->createView()
]
);
}
/**
* @param $id
* @return null|Project
*/
protected function getById($id)
{
/* @var $repo ProjectRepository */
$repo = $this->getDoctrine()->getRepository(Project::class);
$activity = $repo->getById($id);
if (null === $activity) {
throw new NotFoundHttpException('Project "'.$id.'" does not exist');
}
return $activity;
}
/**
* @param Project $project
* @return \Symfony\Component\Form\Form
*/
private function createEditForm(Project $project)
{
return $this->createForm(
ProjectEditForm::class,
$project,
[
'action' => $this->generateUrl('admin_project_edit', ['id' => $project->getId()]),
'method' => 'POST',
'currency' => $project->getCurrency()
]
);
}
}

View File

@@ -12,6 +12,7 @@
namespace TimesheetBundle\DataFixtures\ORM;
use AppBundle\Entity\User;
use Symfony\Component\Intl\Intl;
use TimesheetBundle\Entity\Activity;
use TimesheetBundle\Entity\Customer;
use TimesheetBundle\Entity\Project;
@@ -173,6 +174,7 @@ class LoadFixtures extends AppBundleLoadFixtures
$entry = new Project();
$entry->setName($this->getRandomProject());
$entry->setCurrency($this->getRandomCurrency());
$entry->setBudget(rand(1000, 100000));
$entry->setComment($this->getRandomPhrase());
$entry->setCustomer($allCustomer[rand(1, $amountCustomer)]);
@@ -202,6 +204,9 @@ class LoadFixtures extends AppBundleLoadFixtures
$manager->flush();
}
/**
* @return string[]
*/
private function getActivities()
{
return [
@@ -213,33 +218,51 @@ class LoadFixtures extends AppBundleLoadFixtures
'Internal',
'Research',
'Meeting',
'Hosting',
'Relaunch',
'Support',
'Refactoring',
'Interview',
];
}
/**
* @return string
*/
private function getRandomActivity()
{
$all = $this->getActivities();
return $all[array_rand($all)];
}
/**
* @return string[]
*/
private function getProjects()
{
return [
'FooBar',
'Relaunch',
'Refactoring',
'User Experience',
'Database Migration',
'Test Automatisation',
'Website redesign',
'Services',
'Website Redesign',
'API Development',
'Hosting & Server',
'Customer Relations',
];
}
/**
* @return string
*/
private function getRandomProject()
{
$all = $this->getProjects();
return $all[array_rand($all)];
}
/**
* @return string[]
*/
private function getLocations()
{
return [
@@ -258,12 +281,18 @@ class LoadFixtures extends AppBundleLoadFixtures
];
}
/**
* @return string
*/
private function getRandomLocation()
{
$all = $this->getLocations();
return $all[array_rand($all)];
}
/**
* @return string[]
*/
private function getCustomers()
{
return [
@@ -280,9 +309,37 @@ class LoadFixtures extends AppBundleLoadFixtures
];
}
/**
* @return string
*/
private function getRandomCustomer()
{
$all = $this->getCustomers();
return $all[array_rand($all)];
}
/**
* @return string[]
*/
private function getCurrencies()
{
return [
'EUR',
'GBP',
'USD',
'RUB',
'JPY',
'CNY',
'INR'
];
}
/**
* @return string
*/
private function getRandomCurrency()
{
$all = $this->getCurrencies();
return $all[array_rand($all)];
}
}

View File

@@ -68,10 +68,17 @@ class Project
*/
private $budget = 0.00;
/**
* @var string
*
* @ORM\Column(name="currency", type="string", length=3, nullable=false)
*/
private $currency = 'EUR';
/**
* @var Activity[]
*
* @ORM\OneToMany(targetEntity="TimesheetBundle\Entity\Activity", mappedBy="project")
* @ORM\OneToMany(targetEntity="TimesheetBundle\Entity\Activity", mappedBy="project", cascade={"persist", "merge", "remove"})
*/
private $activities;
@@ -85,6 +92,25 @@ class Project
return $this->id;
}
/**
* @return string
*/
public function getCurrency()
{
return $this->currency;
}
/**
* @param string $currency
* @return $this
*/
public function setCurrency($currency)
{
$this->currency = $currency;
return $this;
}
/**
* @return Customer
*/
@@ -199,6 +225,17 @@ class Project
return $this->budget;
}
/**
* @param Activity[] $activities
* @return $this
*/
public function setActivities($activities)
{
$this->activities = $activities;
return $this;
}
/**
* @return Activity[]
*/

View File

@@ -14,6 +14,7 @@ namespace TimesheetBundle\Form;
use AppBundle\Form\Type\YesNoType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TimesheetBundle\Entity\Activity;
@@ -34,20 +35,22 @@ class ActivityEditForm extends AbstractType
{
$builder
// string - length 255
->add('name', null, [
->add('name', TextType::class, [
'label' => 'label.name',
])
// text
->add('comment', TextareaType::class, [
'label' => 'label.comment',
'required' => false,
])
// entity type: project
->add('project', ProjectType::class, [
'label' => 'label.project',
])
// boolean
->add('visible', YesNoType::class, [
'label' => 'label.visible',
])
->add('project', ProjectType::class, [
'label' => 'label.project',
])
;
}

View File

@@ -0,0 +1,88 @@
<?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\Form;
use AppBundle\Form\Type\YesNoType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Intl\Intl;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TimesheetBundle\Entity\Project;
use TimesheetBundle\Form\Type\CustomerType;
/**
* Defines the form used to manipulate Projects.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class ProjectEditForm extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// string - length 255
->add('name', null, [
'label' => 'label.name',
])
// text
->add('comment', TextareaType::class, [
'label' => 'label.comment',
])
// customer
->add('customer', CustomerType::class, [
'label' => 'label.customer',
])
// boolean
->add('visible', YesNoType::class, [
'label' => 'label.visible',
])
// string
->add('budget', MoneyType::class, [
'label' => 'label.budget',
'currency' => $builder->getOption('currency'),
])
// FIXME add budget
// do not allow activity selection as this causes headaches:
// 1. it is a bad UX
// 2. what should happen if they are detached?
/*
->add('activities', EntityType::class, [
'label' => 'label.activity',
'class' => 'TimesheetBundle:Activity',
'multiple' => true,
'expanded' => true
])
*/
;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Project::class,
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'admin_activity_edit',
'currency' => 'EUR,'
]);
}
}

View File

@@ -0,0 +1,44 @@
<?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\Form\Type;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Custom form field type to select a customer.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class CustomerType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => 'TimesheetBundle:Customer',
'choice_label' => 'name',
]);
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return EntityType::class;
}
}

View File

@@ -100,7 +100,7 @@ class ActivityRepository extends EntityRepository
*/
public function findAll($page = 1)
{
return $this->getPager($this->queryLatest(), $page);
return $this->getPager($this->queryAll(), $page);
}
/**

View File

@@ -12,7 +12,7 @@
namespace TimesheetBundle\Repository;
use AppBundle\Entity\User;
use TimesheetBundle\Entity\Timesheet;
use TimesheetBundle\Entity\Project;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Pagerfanta\Adapter\DoctrineORMAdapter;
@@ -27,6 +27,15 @@ use TimesheetBundle\Model\ProjectStatistic;
class ProjectRepository extends EntityRepository
{
/**
* @param $id
* @return null|Project
*/
public function getById($id)
{
return $this->find($id);
}
/**
* Return statistic data for all user.
*

View File

@@ -39,8 +39,10 @@ class TimesheetRepository extends EntityRepository
*/
protected function queryThisMonth($select, User $user = null)
{
$end = new DateTime();
$begin = $end->sub(new \DateInterval("P30D")); // FIXME last month is not last 30 days
$end = new DateTime('last day of this month');
$end->setTime(23, 59, 59);
$begin = new DateTime('first day of this month');
$begin->setTime(0,0,0);
return $this->queryTimeRange($select, $begin, $end, $user);
}
@@ -59,7 +61,7 @@ class TimesheetRepository extends EntityRepository
$qb->select($select)
->from('TimesheetBundle:Timesheet', 't')
->where($qb->expr()->gt('t.begin', ':from'))
->andWhere($qb->expr()->gt('t.end', ':to'))
->andWhere($qb->expr()->lt('t.end', ':to'))
->setParameter('from', $begin, Type::DATETIME)
->setParameter('to', $end, Type::DATETIME);

View File

@@ -28,8 +28,8 @@
<td>
{{ widgets.button_group({
'edit': path('admin_activity_edit', {'id': entry.id}),
'trash': '#'}
) }}
'trash': '#'
}) }}
</td>
</tr>
{% endfor %}

View File

@@ -6,10 +6,13 @@
{% block page_subtitle %}{{ 'admin_project.subtitle'|trans }}{% endblock %}
{% block main %}
{{ include('default/_flash_messages.html.twig') }}
{% if entries.count > 0 %}
{{ tables.data_table_header({
'label.id': '',
'label.name': 'bookmark-o',
'label.customer': 'address-card-o',
'label.comment': 'comment-o',
'label.activity': 'tasks',
'label.budget': 'credit-card',
@@ -21,12 +24,16 @@
<tr>
<td>{{ entry.id }}</td>
<td>{{ entry.name }}</td>
<td>{{ entry.customer.name }}</td>
<td>{{ entry.comment }}</td>
<td>{{ widgets.badge_counter(entry.activities.count) }}</td>
<td>{{ entry.budget|money}}</td>
<td>{{ entry.budget|money(entry.currency) }}</td>
<td>{{ widgets.label_visible(entry.visible) }}</td>
<td>
{{ widgets.button_group({'edit': '#', 'trash': '#'}) }}
{{ widgets.button_group({
'edit': path('admin_project_edit', {'id': entry.id}),
'trash': '#'
}) }}
</td>
</tr>
{% endfor %}

View File

@@ -0,0 +1,15 @@
{% 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 %}
{{ include('default/_flash_messages.html.twig') }}
{{ include('default/_form.html.twig', {
'title': project.name,
'form': form,
'back': path('admin_project')
}) }}
{% endblock %}