feat: replace dropdowns with individual fields for body dimensions

This commit is contained in:
Björn Fromme
2026-05-28 13:59:30 +02:00
parent fa7e5c2091
commit bdf8cffd04
8 changed files with 366 additions and 148 deletions
+35 -34
View File
@@ -6,7 +6,7 @@ namespace App\Form;
use App\Form\Model\ParticipantDto;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -15,35 +15,38 @@ class BodyDimensionsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$shoeSizeChoices = array_combine(
range($options['shoe_size_min'], $options['shoe_size_max']),
range($options['shoe_size_min'], $options['shoe_size_max'])
);
$ranges = $options['body_dimension_ranges'];
$builder
->add('height', ChoiceType::class, [
'label' => 'Körpergröße',
->add('height', IntegerType::class, [
'label' => 'Körpergröße (cm)',
'required' => $options['height_required'],
'expanded' => false,
'multiple' => false,
'placeholder' => 'Keine Angabe',
'choices' => $options['height_choices'],
'empty_data' => null,
'attr' => [
'placeholder' => sprintf('%d - %d', $ranges['height_min'], $ranges['height_max']),
'min' => $ranges['height_min'],
'max' => $ranges['height_max'],
],
])
->add('shoeSize', ChoiceType::class, [
->add('shoeSize', IntegerType::class, [
'label' => 'Schuhgröße',
'required' => $options['shoeSize_required'],
'expanded' => false,
'multiple' => false,
'placeholder' => 'Keine Angabe',
'choices' => $shoeSizeChoices,
'empty_data' => null,
'attr' => [
'placeholder' => sprintf('%d - %d', $ranges['shoe_size_min'], $ranges['shoe_size_max']),
'min' => $ranges['shoe_size_min'],
'max' => $ranges['shoe_size_max'],
],
])
->add('weight', ChoiceType::class, [
'label' => 'Gewicht',
->add('weight', IntegerType::class, [
'label' => 'Gewicht (kg)',
'required' => $options['weight_required'],
'expanded' => false,
'multiple' => false,
'placeholder' => 'Keine Angabe',
'choices' => $options['weight_choices'],
'empty_data' => null,
'attr' => [
'placeholder' => sprintf('%d - %d', $ranges['weight_min'], $ranges['weight_max']),
'min' => $ranges['weight_min'],
'max' => $ranges['weight_max'],
],
]);
}
@@ -51,22 +54,20 @@ class BodyDimensionsType extends AbstractType
{
$resolver->setDefaults([
'data_class' => ParticipantDto::class,
'height_required' => true,
'weight_required' => true,
'shoeSize_required' => true,
'height_choices' => [],
'weight_choices' => [],
'shoe_size_min' => 36,
'shoe_size_max' => 48,
'help' => 'Verleih kann bis 4 Tage vor Anreise in MyE&P nachgebucht werden, sollten dir (noch) nicht alle Angaben vorliegen',
'height_required' => false,
'weight_required' => false,
'shoeSize_required' => false,
'help' => 'Du kannst die Daten auch später nachreichen',
'help_attr' => [
'class' => 'lg:col-span-3 text-sm -mt-2 px-2',
],
]);
$resolver->setRequired(['body_dimension_ranges']);
$resolver->setAllowedTypes('height_required', 'bool');
$resolver->setAllowedTypes('weight_required', 'bool');
$resolver->setAllowedTypes('shoeSize_required', 'bool');
$resolver->setAllowedTypes('height_choices', 'array');
$resolver->setAllowedTypes('weight_choices', 'array');
$resolver->setAllowedTypes('shoe_size_min', 'int');
$resolver->setAllowedTypes('shoe_size_max', 'int');
$resolver->setAllowedTypes('body_dimension_ranges', 'array');
}
}
+25 -42
View File
@@ -49,12 +49,12 @@ class BookingParticipantType extends AbstractType
: $this->createFieldStateProvider;
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($bookingContext) {
$this->onPreSetData($event, $bookingContext);
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($bookingContext, $options) {
$this->onPreSetData($event, $bookingContext, $options);
})
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($bookingContext) {
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($bookingContext, $options) {
$this->processFieldHandlers($event, $bookingContext);
$this->onPreSubmit($event, $bookingContext);
$this->onPreSubmit($event, $bookingContext, $options);
});
}
@@ -78,7 +78,6 @@ class BookingParticipantType extends AbstractType
return;
}
/** @var ParticipantEditDto $data */
$data = $form->getData();
// Process all field handlers for this participant and sync submitted data
@@ -94,23 +93,17 @@ class BookingParticipantType extends AbstractType
/**
* Adds dynamic fields to the form based on participant data.
*
* @param array<string, mixed> $options
*/
private function onPreSetData(FormEvent $event, ?BookingDto $bookingContext): void
private function onPreSetData(FormEvent $event, ?BookingDto $bookingContext, array $options): void
{
/** @var ParticipantEditDto|null $data */
$data = $event->getData();
if (null === $data) {
return;
}
$form = $event->getForm();
// Use bookingContext from wrapper DTO or fallback to passed option
$data = $event->getData();
$bookingDto = $data->bookingContext;
// Add base fields with states applied
$this->addBaseFields($form, $bookingDto, $data->participant->index);
$this->addBaseFields($form, $bookingDto, $data->participant->index, $options);
// Add dynamic fields
$this->addDynamicFields($form, $bookingDto, $data->participant->index);
@@ -118,29 +111,30 @@ class BookingParticipantType extends AbstractType
/**
* Handles form pre-submit events to update field states based on submitted data.
*
* @param array<string, mixed> $options
*/
private function onPreSubmit(FormEvent $event, ?BookingDto $bookingContext): void
private function onPreSubmit(FormEvent $event, ?BookingDto $bookingContext, array $options): void
{
$submittedData = $event->getData();
$form = $event->getForm();
/** @var ParticipantEditDto $data */
$data = $form->getData();
// Use bookingContext from wrapper DTO or fallback to passed option
$bookingDto = $data->bookingContext;
// Rebuild all fields with updated states based on submitted data
$this->rebuildFieldsWithStates($form, $bookingDto, $data->participant->index, $submittedData);
$this->rebuildFieldsWithStates($form, $bookingDto, $data->participant->index, $submittedData, $options);
$event->setData($submittedData);
}
/**
* Adds base fields to the form with field states applied.
*
* @param FormInterface<mixed> $form
* @param array<string, mixed> $options
*/
/** @param FormInterface<mixed> $form */
private function addBaseFields(FormInterface $form, BookingDto $bookingDto, int $participantIndex): void
private function addBaseFields(FormInterface $form, BookingDto $bookingDto, int $participantIndex, array $options): void
{
// Get field states for base fields
$allFieldStates = $this->fieldStateProvider->getAllFieldStates($bookingDto, $participantIndex);
@@ -247,10 +241,7 @@ class BookingParticipantType extends AbstractType
if ($this->fieldStateProvider->shouldIncludeField('bodyDimensions', $bookingDto, $participantIndex)) {
$form->add('bodyDimensions', BodyDimensionsType::class, [
'property_path' => 'participant',
'height_choices' => $form->getConfig()->getOption('height_choices'),
'weight_choices' => $form->getConfig()->getOption('weight_choices'),
'shoe_size_min' => $form->getConfig()->getOption('shoe_size_min'),
'shoe_size_max' => $form->getConfig()->getOption('shoe_size_max'),
'body_dimension_ranges' => $options['body_dimension_ranges'],
]);
}
}
@@ -261,15 +252,11 @@ class BookingParticipantType extends AbstractType
* This method handles dynamic field exclusion during form submission when
* field states change based on submitted data.
*
* @param FormInterface $form The form to modify
* @param FormInterface<mixed> $form The form to modify
* @param BookingDto $bookingDto The booking data for context
* @param int $participantIndex The participant index
* @param array<string, mixed> $submittedData Submitted form data for state calculation
*/
/**
* @param FormInterface<mixed> $form
* @param array<string, mixed> $submittedData
*/
private function removeExcludedFields(FormInterface $form, BookingDto $bookingDto, int $participantIndex, array &$submittedData = []): void
{
foreach (ParticipantDto::DYNAMIC_FIELDS as $fieldName) {
@@ -289,8 +276,9 @@ class BookingParticipantType extends AbstractType
/**
* @param FormInterface<mixed> $form
* @param array<string, mixed> $submittedData
* @param array<string, mixed> $options
*/
private function rebuildFieldsWithStates(FormInterface $form, BookingDto $bookingDto, int $participantIndex, array &$submittedData): void
private function rebuildFieldsWithStates(FormInterface $form, BookingDto $bookingDto, int $participantIndex, array &$submittedData, array $options): void
{
// First, remove fields that should be excluded entirely
$this->removeExcludedFields($form, $bookingDto, $participantIndex, $submittedData);
@@ -304,7 +292,7 @@ class BookingParticipantType extends AbstractType
}
// Re-add base fields with updated states
$this->addBaseFields($form, $bookingDto, $participantIndex);
$this->addBaseFields($form, $bookingDto, $participantIndex, $options);
// Rebuild dynamic fields
foreach (ParticipantDto::DYNAMIC_FIELDS as $fieldName) {
@@ -415,10 +403,6 @@ class BookingParticipantType extends AbstractType
'data_class' => ParticipantEditDto::class,
'selected_rooms' => [],
'booking_context' => null,
'height_choices' => [],
'weight_choices' => [],
'shoe_size_min' => 36,
'shoe_size_max' => 48,
'validation_groups' => function (FormInterface $form) {
/** @var ParticipantEditDto $data */
$data = $form->getData();
@@ -443,11 +427,10 @@ class BookingParticipantType extends AbstractType
},
]);
$resolver->setRequired(['body_dimension_ranges']);
$resolver->setAllowedTypes('selected_rooms', 'array');
$resolver->setAllowedTypes('booking_context', ['null', BookingDto::class]);
$resolver->setAllowedTypes('height_choices', 'array');
$resolver->setAllowedTypes('weight_choices', 'array');
$resolver->setAllowedTypes('shoe_size_min', 'int');
$resolver->setAllowedTypes('shoe_size_max', 'int');
$resolver->setAllowedTypes('body_dimension_ranges', 'array');
}
}