Improve weekly hours form (#5528)

This commit is contained in:
Kevin Papst
2025-06-06 15:20:00 +02:00
committed by GitHub
parent 2f2ebd6293
commit c8b4e4eabb
7 changed files with 119 additions and 82 deletions

View File

@@ -18,6 +18,7 @@ use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\All;
use Symfony\Component\Validator\Constraints\Valid;
@@ -92,13 +93,16 @@ final class QuickEntryForm extends AbstractType
'entry_options' => [
'label' => false,
'duration_minutes' => $this->configuration->getTimesheetIncrementDuration(),
// this is NOT the start_date, because it would prevent projects from appearing
// in the first days of the week, if the projects ends at the end of the week
// the validation still triggers if the user selects days outside the project range
// this is NOT the start_date, because it would prevent projects from appearing in the
// first days of the week if the project ends at the end of the week.
// the validation still triggers if the user selects days outside the project range.
'start_date' => $options['end_date'],
'end_date' => $options['end_date'],
'empty_data' => function (FormInterface $form) use ($options) {
return clone $options['prototype_data'];
if ($options['prototype_data'] instanceof QuickEntryModel) {
return clone $options['prototype_data'];
}
throw new \Exception('Invalid Prototype given');
},
'prototype_data' => clone $options['prototype_data'],
],
@@ -111,6 +115,25 @@ final class QuickEntryForm extends AbstractType
]);
}
public function finishView(FormView $view, FormInterface $form, array $options): void
{
usort($view['rows']->children, function (FormView $a, FormView $b) {
/** @var \App\Model\QuickEntryModel $objectA */
$objectA = $a->vars['data'];
/** @var \App\Model\QuickEntryModel $objectB */
$objectB = $b->vars['data'];
$existingA = $objectA->hasExistingTimesheet();
$existingB = $objectB->hasExistingTimesheet();
if ($existingA === $existingB) {
return 0;
}
return ($existingA && !$existingB) ? -1 : 1;
});
}
public function configureOptions(OptionsResolver $resolver): void
{
$start = new \DateTime();

View File

@@ -20,7 +20,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
final class QuickEntryTimesheetType extends AbstractType
{
public function __construct(private Security $security)
public function __construct(private readonly Security $security)
{
}
@@ -60,8 +60,28 @@ final class QuickEntryTimesheetType extends AbstractType
$event->getForm()->get('duration')->setData(null);
}
if (null !== $data && !$this->security->isGranted('edit', $data)) {
if ($data instanceof Timesheet && !$this->security->isGranted('edit', $data)) {
$event->getForm()->remove('duration');
$event->getForm()->add('duration', DurationType::class, array_merge(['disabled' => true], $durationOptions));
$mainForm = $event->getForm()->getParent()?->getParent();
if ($mainForm === null) {
return;
}
$isNew = $data->getId() === null;
foreach($mainForm->all() as $key => $child) {
if ($key === 'timesheets') {
continue;
}
if ($child->isDisabled() || $isNew) {
continue;
}
$type = \get_class($child->getConfig()->getType()->getInnerType());
$mainForm->remove($key);
$mainForm->add($key, $type, array_merge($child->getConfig()->getOptions(), ['disabled' => true]));
}
}
}
);

View File

@@ -9,7 +9,6 @@
namespace App\Form\Type;
use App\Entity\User;
use App\Model\QuickEntryModel;
use App\Validator\Constraints\QuickEntryTimesheet;
use DateTime;
@@ -77,21 +76,6 @@ final class QuickEntryWeekType extends AbstractType
};
$builder->addEventListener(FormEvents::PRE_SET_DATA, $activityFunction);
$activityPreSubmitFunction = function (FormEvent $event) use ($activityOptions) {
$data = $event->getData();
if (isset($data['project']) && !empty($data['project'])) {
$activityOptions['projects'] = [$data['project']];
}
if (isset($data['activity']) && !empty($data['activity'])) {
$activityOptions['activities'] = [$data['activity']];
}
$event->getForm()->add('activity', ActivityType::class, $activityOptions);
};
$builder->addEventListener(FormEvents::PRE_SUBMIT, $activityPreSubmitFunction);
$builder->add('metaFields', MetaFieldsCollectionType::class);
$builder->add('timesheets', CollectionType::class, [
@@ -112,13 +96,13 @@ final class QuickEntryWeekType extends AbstractType
]);
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) {
if ($event->getData() === null) {
if ($event->getData() === null && $options['prototype_data'] instanceof QuickEntryModel) {
$event->setData(clone $options['prototype_data']);
}
});
$builder->addModelTransformer(new CallbackTransformer(
function ($transformValue) use ($options) {
function ($transformValue) {
/** @var QuickEntryModel|null $transformValue */
if ($transformValue === null || $transformValue->isPrototype()) {
return $transformValue;
@@ -133,9 +117,6 @@ final class QuickEntryWeekType extends AbstractType
}
$user = $transformValue->getUser();
if ($user === null && $options['user'] instanceof User) {
$user = $options['user'];
}
foreach ($transformValue->getTimesheets() as $timesheet) {
$timesheet->setUser($user);
$timesheet->setProject($project);
@@ -162,9 +143,7 @@ final class QuickEntryWeekType extends AbstractType
$activity = $data->getActivity();
foreach ($newRecords as $record) {
if ($user !== null) {
$record->setUser($user);
}
$record->setUser($user);
if ($project !== null) {
$record->setProject($project);
}