From a43181d60d47b561d4aa419da3bd020ab163db52 Mon Sep 17 00:00:00 2001 From: LenaertsJ Date: Mon, 17 Nov 2025 10:48:15 +0000 Subject: [PATCH] Remove the label if there is only one scope and no scope picking field is displayed. --- .changes/unreleased/UX-20251030-153126.yaml | 6 ++ .../ChillActivityBundle/Form/ActivityType.php | 2 +- .../Form/PersonDocumentType.php | 5 +- .../Form/Type/ScopePickerType.php | 56 +++++++++++++------ .../Tests/Form/Type/ScopePickerTypeTest.php | 15 +++-- .../Service/DocGenerator/PersonContext.php | 3 +- .../ChillTaskBundle/Form/SingleTaskType.php | 8 ++- .../views/SingleTask/Person/list.html.twig | 2 +- .../views/SingleTask/index.html.twig | 2 +- 9 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 .changes/unreleased/UX-20251030-153126.yaml diff --git a/.changes/unreleased/UX-20251030-153126.yaml b/.changes/unreleased/UX-20251030-153126.yaml new file mode 100644 index 000000000..8deaf7701 --- /dev/null +++ b/.changes/unreleased/UX-20251030-153126.yaml @@ -0,0 +1,6 @@ +kind: UX +body: Remove the label if there is only one scope and no scope picking field is displayed. +time: 2025-10-30T15:31:26.807444365+01:00 +custom: + Issue: "449" + SchemaChange: No schema change diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityType.php b/src/Bundle/ChillActivityBundle/Form/ActivityType.php index 28c607acd..3b8f53303 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityType.php @@ -88,8 +88,8 @@ class ActivityType extends AbstractType if (null !== $options['data']->getPerson()) { $builder->add('scope', ScopePickerType::class, [ - 'center' => $options['center'], 'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'], + 'center' => $options['center'], 'required' => true, ]); } diff --git a/src/Bundle/ChillDocStoreBundle/Form/PersonDocumentType.php b/src/Bundle/ChillDocStoreBundle/Form/PersonDocumentType.php index 0addd53cc..6e201165d 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/PersonDocumentType.php +++ b/src/Bundle/ChillDocStoreBundle/Form/PersonDocumentType.php @@ -17,7 +17,6 @@ use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\ScopePickerType; -use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\EntityRepository; @@ -30,7 +29,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class PersonDocumentType extends AbstractType { - public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly ScopeResolverDispatcher $scopeResolverDispatcher, private readonly ParameterBagInterface $parameterBag, private readonly CenterResolverDispatcher $centerResolverDispatcher) {} + public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly ScopeResolverDispatcher $scopeResolverDispatcher, private readonly ParameterBagInterface $parameterBag) {} public function buildForm(FormBuilderInterface $builder, array $options) { @@ -57,8 +56,8 @@ class PersonDocumentType extends AbstractType if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) { $builder->add('scope', ScopePickerType::class, [ - 'center' => $this->centerResolverDispatcher->resolveCenter($document), 'role' => $options['role'], + 'subject' => $document, ]); } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php index 6f10626c6..2367bc565 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php @@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; +use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; @@ -32,65 +33,84 @@ use Symfony\Component\Security\Core\Security; * Allow to pick amongst available scope for the current * user. * - * options : - * - * - `center`: the center of the entity - * - `role` : the role of the user + * Options: + * - `role`: string, the role to check permissions for + * - Either `subject`: object, entity to resolve centers from + * - Or `center`: Center|array|null, the center(s) to check */ class ScopePickerType extends AbstractType { public function __construct( + private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly AuthorizationHelperInterface $authorizationHelper, private readonly Security $security, - private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly CenterResolverManagerInterface $centerResolverManager, ) {} public function buildForm(FormBuilderInterface $builder, array $options) { - $items = array_values( + // Compute centers from subject + $centers = $options['center'] ?? null; + if (null === $centers && isset($options['subject'])) { + $centers = $this->centerResolverManager->resolveCenters($options['subject']); + } + + if (null === $centers) { + throw new \RuntimeException('Either "center" or "subject" must be provided'); + } + + $reachableScopes = array_values( array_filter( $this->authorizationHelper->getReachableScopes( $this->security->getUser(), $options['role'], - $options['center'] + $centers ), static fn (Scope $s) => $s->isActive() ) ); - if (0 === \count($items)) { - throw new \RuntimeException('no scopes are reachable. This form should not be shown to user'); + $builder->setAttribute('reachable_scopes_count', count($reachableScopes)); + + if (0 === count($reachableScopes)) { + $builder->setAttribute('has_scopes', false); + + return; } - if (1 !== \count($items)) { + $builder->setAttribute('has_scopes', true); + + if (1 !== count($reachableScopes)) { $builder->add('scope', EntityType::class, [ 'class' => Scope::class, 'placeholder' => 'Choose the circle', 'choice_label' => fn (Scope $c) => $this->translatableStringHelper->localize($c->getName()), - 'choices' => $items, + 'choices' => $reachableScopes, ]); $builder->setDataMapper(new ScopePickerDataMapper()); } else { $builder->add('scope', HiddenType::class, [ - 'data' => $items[0]->getId(), + 'data' => $reachableScopes[0]->getId(), ]); - $builder->setDataMapper(new ScopePickerDataMapper($items[0])); + $builder->setDataMapper(new ScopePickerDataMapper($reachableScopes[0])); } } public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['fullWidth'] = true; + // display of label is handled by the EntityType + $view->vars['label'] = false; } public function configureOptions(OptionsResolver $resolver) { $resolver - // create `center` option - ->setRequired('center') - ->setAllowedTypes('center', [Center::class, 'array', 'null']) - // create ``role` option ->setRequired('role') - ->setAllowedTypes('role', ['string']); + ->setAllowedTypes('role', ['string']) + ->setDefined('subject') + ->setAllowedTypes('subject', ['object']) + ->setDefined('center') + ->setAllowedTypes('center', [Center::class, 'array', 'null']); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Form/Type/ScopePickerTypeTest.php b/src/Bundle/ChillMainBundle/Tests/Form/Type/ScopePickerTypeTest.php index 3d3813748..53ce413d2 100644 --- a/src/Bundle/ChillMainBundle/Tests/Form/Type/ScopePickerTypeTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Form/Type/ScopePickerTypeTest.php @@ -11,11 +11,11 @@ declare(strict_types=1); namespace Form\Type; -use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\Type\ScopePickerType; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; +use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder; @@ -39,11 +39,11 @@ final class ScopePickerTypeTest extends TypeTestCase { use ProphecyTrait; - public function estBuildOneScopeIsSuccessful() + public function testBuildOneScopeIsSuccessful() { $form = $this->factory->create(ScopePickerType::class, null, [ - 'center' => new Center(), 'role' => 'ONE_SCOPE', + 'center' => [], ]); $view = $form->createView(); @@ -54,8 +54,8 @@ final class ScopePickerTypeTest extends TypeTestCase public function testBuildThreeScopesIsSuccessful() { $form = $this->factory->create(ScopePickerType::class, null, [ - 'center' => new Center(), 'role' => 'THREE_SCOPE', + 'center' => [], ]); $view = $form->createView(); @@ -66,8 +66,8 @@ final class ScopePickerTypeTest extends TypeTestCase public function testBuildTwoScopesIsSuccessful() { $form = $this->factory->create(ScopePickerType::class, null, [ - 'center' => new Center(), 'role' => 'TWO_SCOPE', + 'center' => [], ]); $view = $form->createView(); @@ -101,10 +101,13 @@ final class ScopePickerTypeTest extends TypeTestCase static fn ($args) => $args[0]['fr'] ); + $centerResolverManager = $this->prophesize(CenterResolverManagerInterface::class); + $type = new ScopePickerType( + $translatableStringHelper->reveal(), $authorizationHelper->reveal(), $security->reveal(), - $translatableStringHelper->reveal() + $centerResolverManager->reveal() ); // add the mocks for creating EntityType diff --git a/src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php b/src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php index eb588cdaf..0ac921277 100644 --- a/src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php +++ b/src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php @@ -168,9 +168,8 @@ final readonly class PersonContext implements PersonContextInterface if ($this->isScopeNecessary($entity)) { $builder->add('scope', ScopePickerType::class, [ - 'center' => $this->centerResolverManager->resolveCenters($entity), 'role' => PersonDocumentVoter::CREATE, - 'label' => 'Scope', + 'subject' => $entity, ]); } } diff --git a/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php b/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php index 7e572e078..1883263a1 100644 --- a/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php +++ b/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php @@ -27,7 +27,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class SingleTaskType extends AbstractType { - public function __construct(private readonly ParameterBagInterface $parameterBag, private readonly CenterResolverDispatcherInterface $centerResolverDispatcher, private readonly ScopeResolverDispatcher $scopeResolverDispatcher) {} + public function __construct( + private readonly ParameterBagInterface $parameterBag, + private readonly CenterResolverDispatcherInterface $centerResolverDispatcher, + private readonly ScopeResolverDispatcher $scopeResolverDispatcher, + ) {} public function buildForm(FormBuilderInterface $builder, array $options) { @@ -64,8 +68,8 @@ class SingleTaskType extends AbstractType if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) { $builder ->add('circle', ScopePickerType::class, [ - 'center' => $center, 'role' => $options['role'], + 'subject' => $task, 'required' => true, ]); } diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/list.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/list.html.twig index ffdd17a39..a4d5d81c9 100644 --- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/list.html.twig +++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/list.html.twig @@ -5,7 +5,7 @@ {% block title 'Tasks for {{ name }}'|trans({ '{{ name }}' : person|chill_entity_render_string }) %} {% block content %} -
+

{{ block('title') }}

diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/index.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/index.html.twig index 02ab79664..658d9cba1 100644 --- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/index.html.twig +++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/index.html.twig @@ -37,7 +37,7 @@ {% endblock %} {% else %} {% block content %} -
+
{% include '@ChillTask/SingleTask/AccompanyingCourse/list.html.twig' %}