refactor: split the questionnaire into teamer and bus feedback

This commit is contained in:
fromme committed 2026-10-27 17:22:22 +01:00
1 parent 2f4f9800dd
commit a0170f5952
35 files changed
+1114 -554

No files matched your search

+47 -29
View File
@@ -21,8 +21,10 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Builds a questionnaire form from its definition. The data is the flat answers array keyed by
* question key; each section is a child with inherit_data, so it groups the fields on screen
* Builds one form from one or more questionnaire definitions - a teamer after a bus escort answers
* teamer_feedback and bus_feedback on one page. The data is keyed by questionnaire key, each
* holding that questionnaire's flat answers keyed by question key: every questionnaire is a child
* of its own, and each of its sections a child with inherit_data, which groups the fields on screen
* without nesting the answers.
*
* Two buttons: "draft" saves whatever is filled in, "submit" also demands every required question
@@ -39,21 +41,32 @@ class QuestionnaireType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options): void
{
/** @var Definition $definition */
$definition = $options['definition'];
/** @var list<Definition> $definitions */
$definitions = $options['definitions'];
foreach ($definition->sections as $section) {
$sectionBuilder = $builder->create($section->key, FormType::class, [
'inherit_data' => true,
'label' => $section->title ?? false,
'block_prefix' => 'questionnaire_section',
foreach ($definitions as $definition) {
$questionnaireBuilder = $builder->create($definition->key, FormType::class, [
'label' => $definition->title,
'help' => $definition->description,
'empty_data' => [],
'block_prefix' => 'questionnaire_part',
]);
foreach ($section->questions as $question) {
$sectionBuilder->add($question->key, ...$this->field($question));
foreach ($definition->sections as $section) {
$sectionBuilder = $builder->create($section->key, FormType::class, [
'inherit_data' => true,
'label' => $section->title ?? false,
'block_prefix' => 'questionnaire_section',
]);
foreach ($section->questions as $question) {
$sectionBuilder->add($question->key, ...$this->field($definition, $question));
}
$questionnaireBuilder->add($sectionBuilder);
}
$builder->add($sectionBuilder);
$builder->add($questionnaireBuilder);
}
$builder
@@ -63,24 +76,26 @@ class QuestionnaireType extends AbstractType
->add(self::SUBMIT, SubmitType::class, [
'label' => 'Absenden',
])
->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($definition): void {
->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($definitions): void {
$form = $event->getForm();
if (!$this->isFinal($form)) {
return;
}
$answers = $form->getData();
foreach ($definitions as $definition) {
$answers = $form->getData()[$definition->key] ?? [];
foreach ($definition->sections as $section) {
foreach ($section->questions as $question) {
if ($question->required
&& $definition->isVisible($question, $answers)
&& $this->isEmpty($answers[$question->key] ?? null)
) {
$form->get($section->key)->get($question->key)->addError(
new FormError('Bitte beantworte diese Frage.')
);
foreach ($definition->sections as $section) {
foreach ($section->questions as $question) {
if ($question->required
&& $definition->isVisible($question, $answers)
&& $this->isEmpty($answers[$question->key] ?? null)
) {
$form->get($definition->key)->get($section->key)->get($question->key)->addError(
new FormError('Bitte beantworte diese Frage.')
);
}
}
}
}
@@ -91,8 +106,10 @@ class QuestionnaireType extends AbstractType
public function configureOptions(OptionsResolver $resolver): void
{
$resolver
->setRequired('definition')
->setAllowedTypes('definition', Definition::class)
->setRequired('definitions')
->setAllowedTypes('definitions', Definition::class.'[]')
->setAllowedValues('definitions', static fn (array $definitions): bool => [] !== $definitions
&& count($definitions) === count(array_unique(array_map(static fn (Definition $definition): string => $definition->key, $definitions))))
->setDefaults([
'data_class' => null,
'empty_data' => [],
@@ -114,16 +131,17 @@ class QuestionnaireType extends AbstractType
/**
* @return array{0: class-string, 1: array<string, mixed>}
*/
private function field(Question $question): array
private function field(Definition $definition, Question $question): array
{
$common = [
'label' => $question->label,
'help' => $question->help,
'required' => false,
// read by assets/controllers/questionnaire_controller.js
'row_attr' => ['data-question-key' => $question->key] + (null === $question->showIf ? [] : [
// read by assets/controllers/questionnaire_controller.js; prefixed with the questionnaire,
// since question keys are only unique within one
'row_attr' => ['data-question-key' => $definition->key.'.'.$question->key] + (null === $question->showIf ? [] : [
'data-show-if' => json_encode([
'question' => $question->showIf->question,
'question' => $definition->key.'.'.$question->showIf->question,
'operator' => $question->showIf->operator->value,
'value' => $question->showIf->value,
], JSON_THROW_ON_ERROR),