From e594b65d1e870306c5a942a81d63ea1719f7e5b1 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Mon, 28 Apr 2025 16:32:48 +0200 Subject: [PATCH 01/37] Create event theme admin entity --- .../Controller/EventThemeController.php | 44 +++++ .../ChillEventExtension.php | 44 ++++- .../ChillEventBundle/Entity/EventTheme.php | 183 ++++++++++++++++++ .../ChillEventBundle/Form/EventThemeType.php | 67 +++++++ .../Menu/AdminMenuBuilder.php | 8 +- .../views/Admin/EventTheme/edit.html.twig | 26 +++ .../views/Admin/EventTheme/index.html.twig | 45 +++++ .../views/Admin/EventTheme/new.html.twig | 11 ++ .../views/Entity/event_theme.html.twig | 13 ++ .../Templating/Entity/EventThemeRender.php | 109 +++++++++++ .../ChillEventBundle/config/services.yaml | 10 +- .../config/services/controller.yaml | 4 + .../config/services/forms.yaml | 4 + .../migrations/Version20250428092611.php | 40 ++++ .../translations/messages.fr.yml | 7 + 15 files changed, 608 insertions(+), 7 deletions(-) create mode 100644 src/Bundle/ChillEventBundle/Controller/EventThemeController.php create mode 100644 src/Bundle/ChillEventBundle/Entity/EventTheme.php create mode 100644 src/Bundle/ChillEventBundle/Form/EventThemeType.php create mode 100644 src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/edit.html.twig create mode 100644 src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/index.html.twig create mode 100644 src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/new.html.twig create mode 100644 src/Bundle/ChillEventBundle/Resources/views/Entity/event_theme.html.twig create mode 100644 src/Bundle/ChillEventBundle/Templating/Entity/EventThemeRender.php create mode 100644 src/Bundle/ChillEventBundle/migrations/Version20250428092611.php diff --git a/src/Bundle/ChillEventBundle/Controller/EventThemeController.php b/src/Bundle/ChillEventBundle/Controller/EventThemeController.php new file mode 100644 index 000000000..027b99241 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Controller/EventThemeController.php @@ -0,0 +1,44 @@ + 'create']); + } + + if ('edit' === $action) { + return parent::createFormFor($action, $entity, $formClass, ['step' => 'edit']); + } + + throw new \LogicException('action is not supported: '.$action); + } + + /** + * @param QueryBuilder|mixed $query + */ + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator): QueryBuilder + { + /* @var QueryBuilder $query */ + return $query->orderBy('e.ordering', 'ASC') + ->addOrderBy('e.id', 'ASC'); + } +} diff --git a/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php b/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php index 0b30ca6c5..ecc072a58 100644 --- a/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php +++ b/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php @@ -11,6 +11,9 @@ declare(strict_types=1); namespace Chill\EventBundle\DependencyInjection; +use Chill\EventBundle\Controller\EventThemeController; +use Chill\EventBundle\Entity\EventTheme; +use Chill\EventBundle\Form\EventThemeType; use Chill\EventBundle\Security\EventVoter; use Chill\EventBundle\Security\ParticipationVoter; use Symfony\Component\Config\FileLocator; @@ -26,7 +29,10 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension; */ class ChillEventExtension extends Extension implements PrependExtensionInterface { - public function load(array $configs, ContainerBuilder $container) + /** + * @throws \Exception + */ + public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); @@ -45,16 +51,17 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface /** (non-PHPdoc). * @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend() */ - public function prepend(ContainerBuilder $container) + public function prepend(ContainerBuilder $container): void { $this->prependAuthorization($container); + $this->prependCruds($container); $this->prependRoute($container); } /** * add authorization hierarchy. */ - protected function prependAuthorization(ContainerBuilder $container) + protected function prependAuthorization(ContainerBuilder $container): void { $container->prependExtensionConfig('security', [ 'role_hierarchy' => [ @@ -70,7 +77,7 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface /** * add route to route loader for chill. */ - protected function prependRoute(ContainerBuilder $container) + protected function prependRoute(ContainerBuilder $container): void { // add routes for custom bundle $container->prependExtensionConfig('chill_main', [ @@ -81,4 +88,33 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface ], ]); } + + protected function prependCruds(ContainerBuilder $container): void + { + $container->prependExtensionConfig('chill_main', [ + 'cruds' => [ + [ + 'class' => EventTheme::class, + 'name' => 'event_theme', + 'base_path' => '/admin/event/theme', + 'form_class' => EventThemeType::class, + 'controller' => EventThemeController::class, + 'actions' => [ + 'index' => [ + 'template' => '@ChillEvent/Admin/EventTheme/index.html.twig', + 'role' => 'ROLE_ADMIN', + ], + 'new' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillEvent/Admin/EventTheme/new.html.twig', + ], + 'edit' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillEvent/Admin/EventTheme/edit.html.twig', + ], + ], + ], + ], + ]); + } } diff --git a/src/Bundle/ChillEventBundle/Entity/EventTheme.php b/src/Bundle/ChillEventBundle/Entity/EventTheme.php new file mode 100644 index 000000000..902fc32fb --- /dev/null +++ b/src/Bundle/ChillEventBundle/Entity/EventTheme.php @@ -0,0 +1,183 @@ + + */ + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: EventTheme::class)] + private Collection $children; + + #[ORM\ManyToOne(targetEntity: EventTheme::class, inversedBy: 'children')] + private ?EventTheme $parent = null; + + #[ORM\Column(name: 'ordering', type: Types::FLOAT, options: ['default' => '0.0'])] + private float $ordering = 0.0; + + /** + * Constructor. + */ + public function __construct() + { + $this->children = new ArrayCollection(); + } + + /** + * Get active. + */ + public function getIsActive(): bool + { + return $this->isActive; + } + + /** + * Get id. + */ + public function getId(): ?int + { + return $this->id; + } + + /** + * Get label. + */ + public function getName(): array + { + return $this->name; + } + + /** + * Set active. + * + * @param bool $active + * @return EventTheme + */ + public function setIsActive(bool $active): static + { + $this->isActive = $active; + + return $this; + } + + /** + * Set label. + * + * @param array $label + * @return EventTheme + */ + public function setName(array $label): static + { + $this->name = $label; + + return $this; + } + + public function addChild(self $child): self + { + if (!$this->children->contains($child)) { + $this->children[] = $child; + } + + return $this; + } + + public function removeChild(self $child): self + { + if ($this->children->removeElement($child)) { + // set the owning side to null (unless already changed) + if ($child->getParent() === $this) { + $child->setParent(null); + } + } + + return $this; + } + + public function getChildren(): Collection + { + return $this->children; + } + + public function getDescendants(): Collection + { + $descendants = new ArrayCollection(); + + foreach ($this->getChildren() as $child) { + if (!$descendants->contains($child)) { + $descendants->add($child); + + foreach ($child->getDescendants() as $descendantsOfChild) { + if (!$descendants->contains($descendantsOfChild)) { + $descendants->add($descendantsOfChild); + } + } + } + } + + return $descendants; + } + + public function hasParent(): bool + { + return null !== $this->parent; + } + + public function getOrdering(): float + { + return $this->ordering; + } + + public function setOrdering(float $ordering): EventTheme + { + $this->ordering = $ordering; + + return $this; + } + + public function getParent(): ?self + { + return $this->parent; + } + + public function setParent(?self $parent): self + { + $this->parent = $parent; + + $parent?->addChild($this); + + return $this; + } +} diff --git a/src/Bundle/ChillEventBundle/Form/EventThemeType.php b/src/Bundle/ChillEventBundle/Form/EventThemeType.php new file mode 100644 index 000000000..f48780641 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Form/EventThemeType.php @@ -0,0 +1,67 @@ +add('name', TranslatableStringFormType::class, [ + 'label' => 'Nom', + ]); + + if ('create' === $options['step']) { + $builder + ->add('parent', EntityType::class, [ + 'class' => EventTheme::class, + 'required' => false, + 'choice_label' => fn (EventTheme $theme): ?string => $this->translatableStringHelper->localize($theme->getName()), + 'mapped' => 'create' === $options['step'], + ]); + } + + $builder + ->add('ordering', NumberType::class, [ + 'required' => true, + 'scale' => 6, + ]) + ->add('isActive', ChoiceType::class, [ + 'choices' => [ + 'Yes' => true, + 'No' => false, + ], + 'expanded' => true, + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => EventTheme::class, + ]); + $resolver->setRequired('step') + ->setAllowedValues('step', ['create', 'edit']); + } +} diff --git a/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php index 9db1713b6..8a6dec1ad 100644 --- a/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php @@ -20,14 +20,14 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface /** * @var AuthorizationCheckerInterface */ - protected $authorizationChecker; + protected AuthorizationCheckerInterface $authorizationChecker; public function __construct(AuthorizationCheckerInterface $authorizationChecker) { $this->authorizationChecker = $authorizationChecker; } - public function buildMenu($menuId, MenuItem $menu, array $parameters) + public function buildMenu($menuId, MenuItem $menu, array $parameters): void { if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) { return; @@ -52,6 +52,10 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface $menu->addChild('Role', [ 'route' => 'chill_event_admin_role', ])->setExtras(['order' => 6530]); + + $menu->addChild('Theme', [ + 'route' => 'chill_crud_event_theme_index', + ])->setExtras(['order' => 6540]); } public static function getMenuIds(): array diff --git a/src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/edit.html.twig new file mode 100644 index 000000000..a63d81c99 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/edit.html.twig @@ -0,0 +1,26 @@ +{% extends '@ChillMain/CRUD/Admin/index.html.twig' %} + +{% block title %} + {% include('@ChillMain/CRUD/_edit_title.html.twig') %} +{% endblock %} + +{% block admin_content %} + {% embed '@ChillMain/CRUD/_edit_content.html.twig' %} + + {% block crud_content_form_rows %} + {{ form_row(form.name) }} +
+ +
+ {{ entity.parent|chill_entity_render_box }} +
+
+ {{ form_row(form.ordering) }} + {{ form_row(form.isActive) }} + {% endblock crud_content_form_rows %} + + {% block content_form_actions_save_and_show %}{% endblock %} + {% endembed %} +{% endblock admin_content %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/index.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/index.html.twig new file mode 100644 index 000000000..d518ea625 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/index.html.twig @@ -0,0 +1,45 @@ +{% extends '@ChillMain/CRUD/Admin/index.html.twig' %} + +{% block admin_content %} + {% embed '@ChillMain/CRUD/_index.html.twig' %} + {% block table_entities_thead_tr %} + {{ 'Id'|trans }} + {{ 'Title'|trans }} + {{ 'Ordering'|trans }} + {{ 'active'|trans }} +   + {% endblock %} + + {% block table_entities_tbody %} + {% for entity in entities %} + + {{ entity.id }} + + {{ entity|chill_entity_render_box }} + + {{ entity.ordering }} + + {%- if entity.isActive -%} + + {%- else -%} + + {%- endif -%} + + + + + + {% endfor %} + {% endblock %} + + {% block actions_before %} +
  • + {{'Back to the admin'|trans}} +
  • + {% endblock %} + {% endembed %} +{% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/new.html.twig new file mode 100644 index 000000000..7c204dddd --- /dev/null +++ b/src/Bundle/ChillEventBundle/Resources/views/Admin/EventTheme/new.html.twig @@ -0,0 +1,11 @@ +{% extends '@ChillMain/CRUD/Admin/index.html.twig' %} + +{% block title %} + {% include('@ChillMain/CRUD/_new_title.html.twig') %} +{% endblock %} + +{% block admin_content %} + {% embed '@ChillMain/CRUD/_new_content.html.twig' %} + {% block content_form_actions_save_and_show %}{% endblock %} + {% endembed %} +{% endblock admin_content %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Entity/event_theme.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Entity/event_theme.html.twig new file mode 100644 index 000000000..08796d7b2 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Resources/views/Entity/event_theme.html.twig @@ -0,0 +1,13 @@ +{% set reversed_parents = parents|reverse %} + + + {%- for p in reversed_parents %} + + {{ p.name|localize_translatable_string }}{{ options['default.separator'] }} + + {%- endfor -%} + + {{ eventTheme.name|localize_translatable_string }} + + + diff --git a/src/Bundle/ChillEventBundle/Templating/Entity/EventThemeRender.php b/src/Bundle/ChillEventBundle/Templating/Entity/EventThemeRender.php new file mode 100644 index 000000000..ad765d714 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Templating/Entity/EventThemeRender.php @@ -0,0 +1,109 @@ + + */ +class EventThemeRender implements ChillEntityRenderInterface +{ + public const AND_CHILDREN_MENTION = 'show_and_children_mention'; + + public const DEFAULT_ARGS = [ + self::SEPARATOR_KEY => ' > ', + self::SHOW_AND_CHILDREN => false, + self::AND_CHILDREN_MENTION => 'event_theme.and children', + ]; + + public const SEPARATOR_KEY = 'default.separator'; + + /** + * Show a mention "and children" on each EventTheme, if the event theme + * has at least one child. + */ + public const SHOW_AND_CHILDREN = 'show_and_children'; + + public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {} + + /** + * @throws RuntimeError + * @throws SyntaxError + * @throws LoaderError + */ + public function renderBox($eventTheme, array $options): string + { + $options = array_merge(self::DEFAULT_ARGS, $options); + // give some help to twig: an array of parents + $parents = $this->buildParents($eventTheme); + + return $this + ->engine + ->render( + '@ChillEvent/Entity/event_theme.html.twig', + [ + 'eventTheme' => $eventTheme, + 'parents' => $parents, + 'options' => $options, + ] + ); + } + + public function renderString($entity, array $options): string + { + /** @var EventTheme $entity */ + $options = array_merge(self::DEFAULT_ARGS, $options); + + $titles = [$this->translatableStringHelper->localize($entity->getName())]; + + // loop to parent, until root + while ($entity->hasParent()) { + $entity = $entity->getParent(); + $titles[] = $this->translatableStringHelper->localize( + $entity->getTitle() + ); + } + + $titles = \array_reverse($titles); + + $title = \implode($options[self::SEPARATOR_KEY], $titles); + + if ($options[self::SHOW_AND_CHILDREN] && $entity->hasChildren()) { + $title .= ' ('.$this->translator->trans($options[self::AND_CHILDREN_MENTION]).')'; + } + + return $title; + } + + public function supports($entity, array $options): bool + { + return $entity instanceof EventTheme; + } + + private function buildParents(EventTheme $entity): array + { + $parents = []; + + while ($entity->hasParent()) { + $entity = $parents[] = $entity->getParent(); + } + + return $parents; + } +} diff --git a/src/Bundle/ChillEventBundle/config/services.yaml b/src/Bundle/ChillEventBundle/config/services.yaml index cee12a024..b117528a8 100644 --- a/src/Bundle/ChillEventBundle/config/services.yaml +++ b/src/Bundle/ChillEventBundle/config/services.yaml @@ -1,6 +1,7 @@ services: Chill\EventBundle\Controller\: autowire: true + autoconfigure: true resource: '../Controller' tags: ['controller.service_arguments'] @@ -8,4 +9,11 @@ services: autowire: true autoconfigure: true resource: '../Menu/' - tags: ['chill.menu_builder'] \ No newline at end of file + tags: ['chill.menu_builder'] + + Chill\EventBundle\Templating\Entity\: + autowire: true + autoconfigure: true + resource: '../Templating/Entity' + tags: + - 'chill.render_entity' diff --git a/src/Bundle/ChillEventBundle/config/services/controller.yaml b/src/Bundle/ChillEventBundle/config/services/controller.yaml index e69de29bb..2dda648c9 100644 --- a/src/Bundle/ChillEventBundle/config/services/controller.yaml +++ b/src/Bundle/ChillEventBundle/config/services/controller.yaml @@ -0,0 +1,4 @@ +services: + _defaults: + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillEventBundle/config/services/forms.yaml b/src/Bundle/ChillEventBundle/config/services/forms.yaml index 8c81c3e63..205307234 100644 --- a/src/Bundle/ChillEventBundle/config/services/forms.yaml +++ b/src/Bundle/ChillEventBundle/config/services/forms.yaml @@ -31,3 +31,7 @@ services: Chill\EventBundle\Form\Type\PickEventType: tags: - { name: form.type } + + Chill\EventBundle\Form\EventThemeType: + tags: + - { name: form.type } diff --git a/src/Bundle/ChillEventBundle/migrations/Version20250428092611.php b/src/Bundle/ChillEventBundle/migrations/Version20250428092611.php new file mode 100644 index 000000000..811a368ad --- /dev/null +++ b/src/Bundle/ChillEventBundle/migrations/Version20250428092611.php @@ -0,0 +1,40 @@ +addSql('CREATE SEQUENCE chill_event_event_theme_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_event_event_theme (id INT NOT NULL, parent_id INT DEFAULT NULL, isActive BOOLEAN NOT NULL, name JSON NOT NULL, ordering DOUBLE PRECISION DEFAULT \'0.0\' NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_80D7C6B0727ACA70 ON chill_event_event_theme (parent_id)'); + $this->addSql('ALTER TABLE chill_event_event_theme ADD CONSTRAINT FK_80D7C6B0727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_event_event_theme (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_event_event_theme DROP CONSTRAINT FK_80D7C6B0727ACA70'); + $this->addSql('DROP TABLE chill_event_event_theme'); + } +} diff --git a/src/Bundle/ChillEventBundle/translations/messages.fr.yml b/src/Bundle/ChillEventBundle/translations/messages.fr.yml index 6319a0765..15987c463 100644 --- a/src/Bundle/ChillEventBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillEventBundle/translations/messages.fr.yml @@ -140,3 +140,10 @@ event: event_types: Par types d'événement event_dates: Par date d'événement +crud: + event_theme: + title_new: Créér une nouvelle thématique + title_edit: Modifier la thématique + index: + title: Liste des thématiques + add_new: Créér une nouvelle thématique From adc9c47d0a7301fb4babe911a7e2e5139b0063be Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Mon, 28 Apr 2025 16:46:17 +0200 Subject: [PATCH 02/37] Add event theme color for badge --- .../Resources/public/chill/scss/chill_variables.scss | 3 ++- .../Resources/public/chill/scss/render_box.scss | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/chill_variables.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/chill_variables.scss index 2f113f45b..dce2b4a34 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/chill_variables.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/chill_variables.scss @@ -10,8 +10,9 @@ $chill-household-context: #929d69; // Badges colors $social-issue-color: #4bafe8; $social-action-color: $orange; +$event-theme-color: #ecc546; $activity-color: yellowgreen; // budget colors $budget-resource-color: #6d9e63; -$budget-charge-color: #e03851; \ No newline at end of file +$budget-charge-color: #e03851; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/render_box.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/render_box.scss index 230640bbd..6ec463f77 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/render_box.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/render_box.scss @@ -10,7 +10,8 @@ /// SOCIAL-ISSUE AND SOCIAL-ACTION &.entity-social-issue, - &.entity-social-action { + &.entity-social-action, + &.entity-event-theme { margin-right: 0.3em; font-size: 120%; span.badge { @@ -32,4 +33,9 @@ @include badge_social($social-action-color); } } + &.entity-event-theme { + span.badge { + @include badge_social($event-theme-color); + } + } } From e7a1ff1ac8c1c834517ecbb124601894f82d5522 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 29 Apr 2025 10:02:52 +0200 Subject: [PATCH 03/37] Add event theme property to event entity --- src/Bundle/ChillEventBundle/Entity/Event.php | 38 +++++++++++++++---- .../migrations/Version20250429062911.php | 35 +++++++++++++++++ 2 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 src/Bundle/ChillEventBundle/migrations/Version20250429062911.php diff --git a/src/Bundle/ChillEventBundle/Entity/Event.php b/src/Bundle/ChillEventBundle/Entity/Event.php index c065ed94f..28fcf6092 100644 --- a/src/Bundle/ChillEventBundle/Entity/Event.php +++ b/src/Bundle/ChillEventBundle/Entity/Event.php @@ -71,6 +71,10 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter #[ORM\ManyToOne(targetEntity: EventType::class)] private ?EventType $type = null; + #[ORM\ManyToMany(targetEntity: EventTheme::class)] + #[ORM\JoinTable('chill_event_eventtheme')] + private Collection $themes; + #[ORM\Embedded(class: CommentEmbeddable::class, columnPrefix: 'comment_')] private CommentEmbeddable $comment; @@ -96,6 +100,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter $this->participations = new ArrayCollection(); $this->documents = new ArrayCollection(); $this->comment = new CommentEmbeddable(); + $this->themes = new ArrayCollection(); } /** @@ -126,10 +131,27 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter return $this; } + public function getThemes(): Collection + { + return $this->themes; + } + + public function addTheme(EventTheme $theme): self + { + $this->themes->add($theme); + + return $this; + } + + public function removeTheme(EventTheme $theme): void + { + $this->themes->removeElement($theme); + } + /** * @return Center */ - public function getCenter() + public function getCenter(): ?Center { return $this->center; } @@ -137,7 +159,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter /** * @return Scope */ - public function getCircle() + public function getCircle(): ?Scope { return $this->circle; } @@ -147,7 +169,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter * * @return \DateTime */ - public function getDate() + public function getDate(): ?\DateTime { return $this->date; } @@ -157,7 +179,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter * * @return int */ - public function getId() + public function getId(): ?int { return $this->id; } @@ -172,7 +194,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter * * @return string */ - public function getName() + public function getName(): ?string { return $this->name; } @@ -202,7 +224,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter * * @return Scope */ - public function getScope() + public function getScope(): ?Scope { return $this->getCircle(); } @@ -210,7 +232,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter /** * @return EventType */ - public function getType() + public function getType(): ?EventType { return $this->type; } @@ -218,7 +240,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter /** * Remove participation. */ - public function removeParticipation(Participation $participation) + public function removeParticipation(Participation $participation): void { $this->participations->removeElement($participation); } diff --git a/src/Bundle/ChillEventBundle/migrations/Version20250429062911.php b/src/Bundle/ChillEventBundle/migrations/Version20250429062911.php new file mode 100644 index 000000000..9f931eaa1 --- /dev/null +++ b/src/Bundle/ChillEventBundle/migrations/Version20250429062911.php @@ -0,0 +1,35 @@ +addSql('CREATE TABLE chill_event_eventtheme (event_id INT NOT NULL, eventtheme_id INT NOT NULL, PRIMARY KEY(event_id, eventtheme_id))'); + $this->addSql('CREATE INDEX IDX_8D75029771F7E88B ON chill_event_eventtheme (event_id)'); + $this->addSql('CREATE INDEX IDX_8D750297A81D3C55 ON chill_event_eventtheme (eventtheme_id)'); + $this->addSql('ALTER TABLE chill_event_eventtheme ADD CONSTRAINT FK_8D75029771F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_event_eventtheme ADD CONSTRAINT FK_8D750297A81D3C55 FOREIGN KEY (eventtheme_id) REFERENCES chill_event_event_theme (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_event_eventtheme DROP CONSTRAINT FK_8D75029771F7E88B'); + $this->addSql('ALTER TABLE chill_event_eventtheme DROP CONSTRAINT FK_8D750297A81D3C55'); + $this->addSql('DROP TABLE chill_event_eventtheme'); + } +} From 383f5887950bba90042193f35539808b453024a0 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 29 Apr 2025 10:03:42 +0200 Subject: [PATCH 04/37] Add field in event for themes --- .../ChillEventBundle/Entity/EventTheme.php | 5 +-- .../ChillEventBundle/Form/EventType.php | 11 +++++ .../Form/Type/PickEventThemeType.php | 45 +++++++++++++++++++ .../Form/Type/PickEventTypeType.php | 5 +-- .../Repository/EventThemeRepository.php | 38 ++++++++++++++++ .../Templating/Entity/EventThemeRender.php | 8 ++-- .../config/services/forms.yaml | 8 +++- 7 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php create mode 100644 src/Bundle/ChillEventBundle/Repository/EventThemeRepository.php diff --git a/src/Bundle/ChillEventBundle/Entity/EventTheme.php b/src/Bundle/ChillEventBundle/Entity/EventTheme.php index 902fc32fb..28bc87e04 100644 --- a/src/Bundle/ChillEventBundle/Entity/EventTheme.php +++ b/src/Bundle/ChillEventBundle/Entity/EventTheme.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\EventBundle\Entity; +use Chill\EventBundle\Repository\EventThemeRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; @@ -19,8 +20,8 @@ use Doctrine\ORM\Mapping as ORM; /** * Class EventTheme. */ -#[ORM\Entity] #[ORM\HasLifecycleCallbacks] +#[ORM\Entity(repositoryClass: EventThemeRepository::class)] #[ORM\Table(name: 'chill_event_event_theme')] class EventTheme { @@ -82,7 +83,6 @@ class EventTheme /** * Set active. * - * @param bool $active * @return EventTheme */ public function setIsActive(bool $active): static @@ -95,7 +95,6 @@ class EventTheme /** * Set label. * - * @param array $label * @return EventTheme */ public function setName(array $label): static diff --git a/src/Bundle/ChillEventBundle/Form/EventType.php b/src/Bundle/ChillEventBundle/Form/EventType.php index ebdf66010..77844aaba 100644 --- a/src/Bundle/ChillEventBundle/Form/EventType.php +++ b/src/Bundle/ChillEventBundle/Form/EventType.php @@ -14,7 +14,10 @@ namespace Chill\EventBundle\Form; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Form\StoredObjectType; use Chill\EventBundle\Entity\Event; +use Chill\EventBundle\Entity\EventTheme; +use Chill\EventBundle\Form\Type\PickEventThemeType; use Chill\EventBundle\Form\Type\PickEventTypeType; +use Chill\EventBundle\Repository\EventThemeRepository; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillDateTimeType; @@ -22,14 +25,19 @@ use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\PickUserDynamicType; use Chill\MainBundle\Form\Type\PickUserLocationType; use Chill\MainBundle\Form\Type\ScopePickerType; +use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +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\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; class EventType extends AbstractType { + public function __construct(private readonly EventThemeRepository $eventThemeRepository, private readonly TranslatorInterface $translator, private readonly TranslatableStringHelperInterface $translatableStringHelper) {} + public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -49,6 +57,9 @@ class EventType extends AbstractType 'class' => '', ], ]) + ->add('themes', PickEventThemeType::class, [ + 'multiple' => true, + ]) ->add('moderator', PickUserDynamicType::class, [ 'label' => 'Pick a moderator', ]) diff --git a/src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php b/src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php new file mode 100644 index 000000000..3010df271 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php @@ -0,0 +1,45 @@ +setDefaults([ + 'class' => EventTheme::class, + 'choices' => $this->eventThemeRepository->findByActiveOrdered(), + 'choice_label' => fn (EventTheme $et) => $this->eventThemeRender->renderString($et, []), + 'placeholder' => 'event.form.Select one or more themes', + 'required' => true, + 'attr' => ['class' => 'select2'], + 'label' => 'event.theme.label', + 'multiple' => false, + ]) + ->setAllowedTypes('multiple', ['bool']); + } + + public function getParent(): string + { + return EntityType::class; + } +} diff --git a/src/Bundle/ChillEventBundle/Form/Type/PickEventTypeType.php b/src/Bundle/ChillEventBundle/Form/Type/PickEventTypeType.php index d027c05cf..472fdd463 100644 --- a/src/Bundle/ChillEventBundle/Form/Type/PickEventTypeType.php +++ b/src/Bundle/ChillEventBundle/Form/Type/PickEventTypeType.php @@ -23,10 +23,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; */ class PickEventTypeType extends AbstractType { - /** - * @var TranslatableStringHelper - */ - protected $translatableStringHelper; + protected TranslatableStringHelper $translatableStringHelper; public function __construct(TranslatableStringHelper $helper) { diff --git a/src/Bundle/ChillEventBundle/Repository/EventThemeRepository.php b/src/Bundle/ChillEventBundle/Repository/EventThemeRepository.php new file mode 100644 index 000000000..c8956356b --- /dev/null +++ b/src/Bundle/ChillEventBundle/Repository/EventThemeRepository.php @@ -0,0 +1,38 @@ + + */ +class EventThemeRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, EventTheme::class); + } + + public function findByActiveOrdered(): array + { + return $this->createQueryBuilder('t') + ->select('t') + ->where('t.isActive = True') + ->orderBy('t.ordering', 'ASC') + ->getQuery() + ->getResult(); + } + +} diff --git a/src/Bundle/ChillEventBundle/Templating/Entity/EventThemeRender.php b/src/Bundle/ChillEventBundle/Templating/Entity/EventThemeRender.php index ad765d714..6a10b61f7 100644 --- a/src/Bundle/ChillEventBundle/Templating/Entity/EventThemeRender.php +++ b/src/Bundle/ChillEventBundle/Templating/Entity/EventThemeRender.php @@ -47,18 +47,18 @@ class EventThemeRender implements ChillEntityRenderInterface * @throws SyntaxError * @throws LoaderError */ - public function renderBox($eventTheme, array $options): string + public function renderBox($entity, array $options): string { $options = array_merge(self::DEFAULT_ARGS, $options); // give some help to twig: an array of parents - $parents = $this->buildParents($eventTheme); + $parents = $this->buildParents($entity); return $this ->engine ->render( '@ChillEvent/Entity/event_theme.html.twig', [ - 'eventTheme' => $eventTheme, + 'eventTheme' => $entity, 'parents' => $parents, 'options' => $options, ] @@ -76,7 +76,7 @@ class EventThemeRender implements ChillEntityRenderInterface while ($entity->hasParent()) { $entity = $entity->getParent(); $titles[] = $this->translatableStringHelper->localize( - $entity->getTitle() + $entity->getName() ); } diff --git a/src/Bundle/ChillEventBundle/config/services/forms.yaml b/src/Bundle/ChillEventBundle/config/services/forms.yaml index 205307234..9264a09ee 100644 --- a/src/Bundle/ChillEventBundle/config/services/forms.yaml +++ b/src/Bundle/ChillEventBundle/config/services/forms.yaml @@ -32,6 +32,12 @@ services: tags: - { name: form.type } - Chill\EventBundle\Form\EventThemeType: + Chill\EventBundle\Form\Type\PickEventThemeType: tags: - { name: form.type } + + Chill\EventBundle\Form\EventType: + tags: + - { name: form.type } + + From 27f0bf28e9324eb924a127c7ea3606f21821567b Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 29 Apr 2025 10:03:57 +0200 Subject: [PATCH 05/37] Adjust templates and translations --- .../Menu/AdminMenuBuilder.php | 5 +- .../Resources/views/Event/edit.html.twig | 1 + .../Resources/views/Event/new.html.twig | 4 +- .../Resources/views/Event/page_list.html.twig | 5 + .../Resources/views/Event/show.html.twig | 191 +++++++++--------- .../translations/messages.fr.yml | 3 + 6 files changed, 112 insertions(+), 97 deletions(-) diff --git a/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php index 8a6dec1ad..07fd81734 100644 --- a/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php +++ b/src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php @@ -17,9 +17,6 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; class AdminMenuBuilder implements LocalMenuBuilderInterface { - /** - * @var AuthorizationCheckerInterface - */ protected AuthorizationCheckerInterface $authorizationChecker; public function __construct(AuthorizationCheckerInterface $authorizationChecker) @@ -53,7 +50,7 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface 'route' => 'chill_event_admin_role', ])->setExtras(['order' => 6530]); - $menu->addChild('Theme', [ + $menu->addChild('event.theme.label', [ 'route' => 'chill_crud_event_theme_index', ])->setExtras(['order' => 6540]); } diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig index b6b11878b..052a13e26 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig @@ -17,6 +17,7 @@ {{ form_row(edit_form.date) }} {{ form_row(edit_form.type, { label: "Event type" }) }} + {{ form_row(edit_form.themes) }} {{ form_row(edit_form.moderator) }} {{ form_row(edit_form.location) }} {{ form_row(edit_form.organizationCost) }} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig index 0fb69a4ea..898af74c1 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig @@ -13,10 +13,12 @@ {{ form_start(form) }} {{ form_errors(form) }} - {{ form_row(form.circle) }} +{# {{ form_row(form.circle) }}#} {{ form_row(form.name) }} + {{ form_row(form.circle) }} {{ form_row(form.date) }} {{ form_row(form.type, { label: "Event type" }) }} + {{ form_row(form.themes) }} {{ form_row(form.moderator) }} {{ form_row(form.location) }} {{ form_row(form.organizationCost) }} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig index 4d887d3c5..bf6ab48f0 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig @@ -41,6 +41,11 @@ block js %} {{ e.moderator | chill_entity_render_box }}

    {% endif %} +
    + {% for t in e.themes %} + {{ t|chill_entity_render_box }} + {% endfor %} +
    diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig index 7d8bf1fc0..b124c528b 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig @@ -38,6 +38,14 @@ {{ 'Event type'|trans }} {{ event.type.name|localize_translatable_string }} + + {{ 'event.theme.label'|trans }} + + {% for t in event.themes %} + {{ t|chill_entity_render_box }} + {% endfor %} + + {{ 'Moderator'|trans }} {{ event.moderator|trans|default('-') }} @@ -80,6 +88,97 @@
    {% endif %} +
    +

    {{ 'Participations'|trans }}

    + {% set count = event.participations|length %} +

    {{ 'count participations to this event'|trans({'count': count}) }}

    + + {% if count > 0 %} + + + + + + + + + + + + {% for participation in event.participations %} + + + + + + + + {% endfor %} + +
    {{ 'Person'|trans }}{{ 'Role'|trans }}{{ 'Status'|trans }}{{ 'Last update'|trans }} 
    + {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { + targetEntity: { name: 'person', id: participation.person.id }, + action: 'show', + displayBadge: true, + buttonText: participation.person|chill_entity_render_string, + isDead: participation.person.deathdate is not null + } %} + {{ participation.role.name|localize_translatable_string }}{{ participation.status.name|localize_translatable_string }}{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned, + alternative: knplabs/knp-time-bundle provide filter 'ago' #} + + +
      + {% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %} +
    • + +
    • +
    • + +
    • + {% endif %} +
    +
    + + + {% endif %} + +
    +
    + {{ form_start(form_add_participation_by_person) }} +
    + {{ form_widget(form_add_participation_by_person.person_id, { 'attr' : { + 'class' : 'custom-select', + 'style': 'min-width: 15em; max-width: 18em; display: inline-block;' + }} ) }} +
    + + {{ form_end(form_add_participation_by_person) }} +
    +
    + +
      + {% if count > 0 %} +
    • + {{ form_start(form_export, {'attr': {'id': 'export_tableur'}}) }} +
      + {{ form_widget(form_export.format, { 'attr' : { 'class': 'custom-select' } }) }} +
      + {{ form_widget(form_export.submit, { 'attr' : { 'class': 'btn btn-save' } }) }} +
      + +
      + {{ form_rest(form_export) }} + {{ form_end(form_export) }} +
    • +
    • {{ 'Edit all the participations'|trans }}
    • + {% endif %} +
    +
    +
    + {{ chill_delegated_block('block_footer_show', { 'event': event }) }} +
      @@ -100,97 +199,5 @@
    - -

    {{ 'Participations'|trans }}

    - {% set count = event.participations|length %} -

    {{ 'count participations to this event'|trans({'count': count}) }}

    - - {% if count > 0 %} - - - - - - - - - - - - {% for participation in event.participations %} - - - - - - - - {% endfor %} - -
    {{ 'Person'|trans }}{{ 'Role'|trans }}{{ 'Status'|trans }}{{ 'Last update'|trans }} 
    - {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { - targetEntity: { name: 'person', id: participation.person.id }, - action: 'show', - displayBadge: true, - buttonText: participation.person|chill_entity_render_string, - isDead: participation.person.deathdate is not null - } %} - {{ participation.role.name|localize_translatable_string }}{{ participation.status.name|localize_translatable_string }}{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned, - alternative: knplabs/knp-time-bundle provide filter 'ago' #} - - -
      - {% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %} -
    • - -
    • -
    • - -
    • - {% endif %} -
    -
    - - - {% endif %} - - - -
    -
    - {{ form_start(form_add_participation_by_person) }} -
    - {{ form_widget(form_add_participation_by_person.person_id, { 'attr' : { - 'class' : 'custom-select', - 'style': 'min-width: 15em; max-width: 18em; display: inline-block;' - }} ) }} -
    - - {{ form_end(form_add_participation_by_person) }} -
    - -
    - {{ form_start(form_export, {'attr': {'id': 'export_tableur'}}) }} -
    - {{ form_widget(form_export.format, { 'attr' : { 'class': 'custom-select' } }) }} -
    - {{ form_widget(form_export.submit, { 'attr' : { 'class': 'btn btn-save' } }) }} -
    - -
    - {{ form_rest(form_export) }} - {{ form_end(form_export) }} -
    -
    - -
    - {{ chill_delegated_block('block_footer_show', { 'event': event }) }} -
    {% endblock %} diff --git a/src/Bundle/ChillEventBundle/translations/messages.fr.yml b/src/Bundle/ChillEventBundle/translations/messages.fr.yml index 15987c463..73f56f27f 100644 --- a/src/Bundle/ChillEventBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillEventBundle/translations/messages.fr.yml @@ -128,6 +128,8 @@ Create a new type: Créer un nouveau type Create a new status: Créer un nouveau statut event: + theme: + label: Thématiques fields: organizationCost: Coût d'organisation location: Localisation @@ -136,6 +138,7 @@ event: organisationCost_help: Coût d'organisation pour la structure. Utile pour les statistiques. add_document: Ajouter un document remove_document: Supprimer le document + Select one or more themes: Selectionnez une ou plusieurs thématiques filter: event_types: Par types d'événement event_dates: Par date d'événement From bb71e084b8d7271744d06759470b95735bc777c9 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 29 Apr 2025 14:31:39 +0200 Subject: [PATCH 06/37] Create address on the fly field in event form --- .../Controller/EventController.php | 6 ++ src/Bundle/ChillEventBundle/Entity/Event.php | 9 +++ .../ChillEventBundle/Form/EventType.php | 22 +++--- .../Resources/public/vuejs/App.vue | 14 ++++ .../Resources/public/vuejs/index.js | 6 ++ .../Resources/public/vuejs/store.js | 79 +++++++++++++++++++ .../Resources/views/Event/new.html.twig | 28 +++++-- .../ChillEventBundle/chill.webpack.config.js | 7 ++ .../vuejs/Address/components/AddAddress.vue | 68 ++++++++++------ .../components/AddAddress/AddressMore.vue | 58 ++++++++++---- .../AddAddress/AddressSelection.vue | 42 +++++++--- .../components/AddAddress/CitySelection.vue | 42 +++++++--- .../AddAddress/CountrySelection.vue | 28 +++++-- .../vuejs/Address/components/EditPane.vue | 21 ++++- .../vuejs/Address/components/ShowPane.vue | 56 +++++++++---- 15 files changed, 386 insertions(+), 100 deletions(-) create mode 100644 src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue create mode 100644 src/Bundle/ChillEventBundle/Resources/public/vuejs/index.js create mode 100644 src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js diff --git a/src/Bundle/ChillEventBundle/Controller/EventController.php b/src/Bundle/ChillEventBundle/Controller/EventController.php index b4f099769..09cb86861 100644 --- a/src/Bundle/ChillEventBundle/Controller/EventController.php +++ b/src/Bundle/ChillEventBundle/Controller/EventController.php @@ -41,6 +41,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Serializer\SerializerInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -59,6 +60,8 @@ final class EventController extends AbstractController private readonly PaginatorFactory $paginator, private readonly Security $security, private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, + private readonly SerializerInterface $serializer, + ) {} #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/delete', name: 'chill_event__event_delete', requirements: ['event_id' => '\d+'], methods: ['GET', 'POST', 'DELETE'])] @@ -202,9 +205,12 @@ final class EventController extends AbstractController return $this->redirectToRoute('chill_event__event_show', ['event_id' => $entity->getId()]); } + $entity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']); + return $this->render('@ChillEvent/Event/new.html.twig', [ 'entity' => $entity, 'form' => $form->createView(), + 'entity_json' => $entity_array, ]); } diff --git a/src/Bundle/ChillEventBundle/Entity/Event.php b/src/Bundle/ChillEventBundle/Entity/Event.php index 28fcf6092..c45c68159 100644 --- a/src/Bundle/ChillEventBundle/Entity/Event.php +++ b/src/Bundle/ChillEventBundle/Entity/Event.php @@ -27,6 +27,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Serializer\Annotation as Serializer; /** * Class Event. @@ -58,6 +59,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter private ?User $moderator = null; #[Assert\NotBlank] + #[Serializer\Groups(['read'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 150)] private ?string $name = null; @@ -65,13 +67,19 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter * @var Collection */ #[ORM\OneToMany(mappedBy: 'event', targetEntity: Participation::class)] + #[Serializer\Groups(['read'])] private Collection $participations; #[Assert\NotNull] + #[Serializer\Groups(['read'])] #[ORM\ManyToOne(targetEntity: EventType::class)] private ?EventType $type = null; + /** + * @var Collection + */ #[ORM\ManyToMany(targetEntity: EventTheme::class)] + #[Serializer\Groups(['read'])] #[ORM\JoinTable('chill_event_eventtheme')] private Collection $themes; @@ -79,6 +87,7 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter private CommentEmbeddable $comment; #[ORM\ManyToOne(targetEntity: Location::class)] + #[Serializer\Groups(['read'])] #[ORM\JoinColumn(nullable: true)] private ?Location $location = null; diff --git a/src/Bundle/ChillEventBundle/Form/EventType.php b/src/Bundle/ChillEventBundle/Form/EventType.php index 77844aaba..6bceed93f 100644 --- a/src/Bundle/ChillEventBundle/Form/EventType.php +++ b/src/Bundle/ChillEventBundle/Form/EventType.php @@ -14,29 +14,27 @@ namespace Chill\EventBundle\Form; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Form\StoredObjectType; use Chill\EventBundle\Entity\Event; -use Chill\EventBundle\Entity\EventTheme; use Chill\EventBundle\Form\Type\PickEventThemeType; use Chill\EventBundle\Form\Type\PickEventTypeType; -use Chill\EventBundle\Repository\EventThemeRepository; use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer; use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillDateTimeType; use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\PickUserDynamicType; -use Chill\MainBundle\Form\Type\PickUserLocationType; use Chill\MainBundle\Form\Type\ScopePickerType; -use Chill\MainBundle\Templating\TranslatableStringHelperInterface; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Contracts\Translation\TranslatorInterface; class EventType extends AbstractType { - public function __construct(private readonly EventThemeRepository $eventThemeRepository, private readonly TranslatorInterface $translator, private readonly TranslatableStringHelperInterface $translatableStringHelper) {} + public function __construct( + private readonly IdToLocationDataTransformer $idToLocationDataTransformer, + ) {} public function buildForm(FormBuilderInterface $builder, array $options) { @@ -63,9 +61,6 @@ class EventType extends AbstractType ->add('moderator', PickUserDynamicType::class, [ 'label' => 'Pick a moderator', ]) - ->add('location', PickUserLocationType::class, [ - 'label' => 'event.fields.location', - ]) ->add('comment', CommentType::class, [ 'label' => 'Comment', 'required' => false, @@ -85,6 +80,10 @@ class EventType extends AbstractType 'label' => 'event.fields.organizationCost', 'help' => 'event.form.organisationCost_help', ]); + + $builder->add('location', HiddenType::class) + ->get('location') + ->addModelTransformer($this->idToLocationDataTransformer); } public function configureOptions(OptionsResolver $resolver) @@ -103,6 +102,7 @@ class EventType extends AbstractType */ public function getBlockPrefix() { - return 'chill_eventbundle_event'; + // as the js shares some hardcoded items from the activity bundle, we have to rewrite block prefix + return 'chill_activitybundle_activity'; } } diff --git a/src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue b/src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue new file mode 100644 index 000000000..89158d742 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/Bundle/ChillEventBundle/Resources/public/vuejs/index.js b/src/Bundle/ChillEventBundle/Resources/public/vuejs/index.js new file mode 100644 index 000000000..e0be75aaf --- /dev/null +++ b/src/Bundle/ChillEventBundle/Resources/public/vuejs/index.js @@ -0,0 +1,6 @@ +import { createApp } from "vue"; + +import App from "./App.vue"; +import store from "./store"; + +createApp(App).use(store).mount("#event"); diff --git a/src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js b/src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js new file mode 100644 index 000000000..ddfa3d512 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js @@ -0,0 +1,79 @@ +import "es6-promise/auto"; +import { createStore } from "vuex"; + +import prepareLocations from "ChillActivityAssets/vuejs/Activity/store.locations"; +import {whoami} from "ChillMainAssets/lib/api/user"; +import {mapEntity} from "ChillCalendarAssets/vuejs/Calendar/store/utils"; +import {postLocation} from "ChillActivityAssets/vuejs/Activity/api"; + +const debug = process.env.NODE_ENV !== "production"; + +const store = createStore({ + strict: debug, + state: { + activity: window.entity, // activity is the event entity in this case (re-using component from activity bundle) + currentEvent: null, + availableLocations: [], + me: null, + }, + getters: { + + }, + actions: { + addAvailableLocationGroup({ commit }, payload) { + commit("addAvailableLocationGroup", payload); + }, + updateLocation({ commit }, value) { + // console.log("### action: updateLocation", value); + let hiddenLocation = document.getElementById( + "chill_activitybundle_activity_location", + ); + if (value.onthefly) { + const body = { + type: "location", + name: + value.name === "__AccompanyingCourseLocation__" ? null : value.name, + locationType: { + id: value.locationType.id, + type: "location-type", + }, + }; + if (value.address.id) { + Object.assign(body, { + address: { + id: value.address.id, + }, + }); + } + postLocation(body) + .then((location) => (hiddenLocation.value = location.id)) + .catch((err) => { + console.log(err.message); + }); + } else { + hiddenLocation.value = value.id; + } + commit("updateLocation", value); + }, + }, + mutations: { + setWhoAmiI(state, me) { + state.me = me; + }, + addAvailableLocationGroup(state, group) { + state.availableLocations.push(group); + }, + updateLocation(state, value) { + // console.log("### mutation: updateLocation", value); + state.activity.location = value; + }, + } +}); + +whoami().then((me) => { + store.commit("setWhoAmiI", me); +}); + +prepareLocations(store); + +export default store; diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig index 898af74c1..279bf4588 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/new.html.twig @@ -1,19 +1,19 @@ -{% extends '@ChillEvent/layout.html.twig' %} {% block js %} -{{ encore_entry_script_tags("mod_async_upload") }} -{{ encore_entry_script_tags("mod_pickentity_type") }} +{% extends '@ChillEvent/layout.html.twig' %} -{% endblock %} {% block css %} +{% block css %} {{ encore_entry_link_tags("mod_async_upload") }} {{ encore_entry_link_tags("mod_pickentity_type") }} +{{ encore_entry_link_tags('vue_event') }} +{% endblock %} -{% endblock %} {% block title 'Event creation'|trans %} {% block event_content --%} +{% block title 'Event creation'|trans %} + +{% block event_content -%}

    {{ "Event creation" | trans }}

    {{ form_start(form) }} {{ form_errors(form) }} -{# {{ form_row(form.circle) }}#} {{ form_row(form.name) }} {{ form_row(form.circle) }} {{ form_row(form.date) }} @@ -21,6 +21,7 @@ {{ form_row(form.themes) }} {{ form_row(form.moderator) }} {{ form_row(form.location) }} +
    {{ form_row(form.organizationCost) }} {{ form_row(form.comment) }} {{ form_row(form.documents) }} @@ -42,5 +43,18 @@ {{ form_end(form) }} + +
    +
    {% endblock %} + +{% block js %} + {{ encore_entry_script_tags("mod_async_upload") }} + {{ encore_entry_script_tags("mod_pickentity_type") }} + {{ encore_entry_script_tags('vue_event') }} + +{% endblock %} diff --git a/src/Bundle/ChillEventBundle/chill.webpack.config.js b/src/Bundle/ChillEventBundle/chill.webpack.config.js index e2c1e14bc..3f13a7773 100644 --- a/src/Bundle/ChillEventBundle/chill.webpack.config.js +++ b/src/Bundle/ChillEventBundle/chill.webpack.config.js @@ -1,3 +1,10 @@ module.exports = function (encore, entries) { entries.push(__dirname + "/Resources/public/chill/index.js"); + + encore.addEntry( + "vue_event", + __dirname + "/Resources/public/vuejs/index.js", + ); }; + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue index 103849e3a..923defdeb 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue @@ -21,10 +21,10 @@ > @@ -43,7 +43,7 @@ @@ -62,13 +62,13 @@ > @@ -85,10 +85,10 @@ > @@ -108,17 +108,17 @@ @@ -139,7 +139,7 @@ > @@ -171,10 +171,10 @@ > @@ -193,10 +193,10 @@ @@ -216,13 +216,13 @@ @@ -244,9 +244,16 @@ import { postPostalCode, } from "../api"; import { - postAddressToPerson, - postAddressToHousehold, -} from "ChillPersonAssets/vuejs/_api/AddAddress.js"; + CREATE_A_NEW_ADDRESS, + ADDRESS_LOADING, + ACTIVITY_CREATE_ADDRESS, + ACTIVITY_EDIT_ADDRESS, + CANCEL, + SAVE, + PREVIOUS, + NEXT, + trans, +} from "translator"; import ShowPane from "./ShowPane.vue"; import SuggestPane from "./SuggestPane.vue"; import EditPane from "./EditPane.vue"; @@ -254,6 +261,17 @@ import DatePane from "./DatePane.vue"; export default { name: "AddAddress", + setup() { + return { + trans, + CREATE_A_NEW_ADDRESS, + ADDRESS_LOADING, + CANCEL, + SAVE, + PREVIOUS, + NEXT + }; + }, props: ["context", "options", "addressChangedCallback"], components: { Modal, @@ -373,9 +391,11 @@ export default { (this.options.title.edit !== null || this.options.title.create !== null) ) { + console.log('this.options.title', this.options.title) + return this.context.edit - ? this.options.title.edit - : this.options.title.create; + ? ACTIVITY_EDIT_ADDRESS + : ACTIVITY_CREATE_ADDRESS; } return this.context.edit ? this.defaultz.title.edit diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue index 4b59efbc6..0be4b7809 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue @@ -1,6 +1,6 @@