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 %}
+
+
+
+ {{ 'Person'|trans }} |
+ {{ 'Role'|trans }} |
+ {{ 'Status'|trans }} |
+ {{ 'Last update'|trans }} |
+ |
+
+
+
+ {% for participation in event.participations %}
+
+
+ {% 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 %}
+
+ |
+
+ {% endfor %}
+
+
+
+
+ {% 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_rest(form_export) }}
+ {{ form_end(form_export) }}
+
+ - {{ 'Edit all the participations'|trans }}
+ {% endif %}
+
+
+
+ {{ chill_delegated_block('block_footer_show', { 'event': event }) }}
+
-
- {{ 'Participations'|trans }}
- {% set count = event.participations|length %}
- {{ 'count participations to this event'|trans({'count': count}) }}
-
- {% if count > 0 %}
-
-
-
- {{ 'Person'|trans }} |
- {{ 'Role'|trans }} |
- {{ 'Status'|trans }} |
- {{ 'Last update'|trans }} |
- |
-
-
-
- {% for participation in event.participations %}
-
-
- {% 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 %}
-
- |
-
- {% endfor %}
-
-
-
-
- {% 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_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 @@
>
- {{ $t(getTextTitle) }}
+ {{ trans(getTextTitle) }}
- {{ $t("loading") }}
+ {{ trans(ADDRESS_LOADING) }}
@@ -43,7 +43,7 @@
@@ -62,13 +62,13 @@
>
- {{ $t("action.cancel") }}
+ {{ trans(CANCEL) }}
@@ -85,10 +85,10 @@
>
- {{ $t(getTextTitle) }}
+ {{ trans(getTextTitle) }}
- {{ $t("loading") }}
+ {{ trans(ADDRESS_LOADING) }}
@@ -108,17 +108,17 @@
-
+
@@ -139,7 +139,7 @@
>
- {{ $t("action.cancel") }}
+ {{ trans(CANCEL) }}
@@ -148,13 +148,13 @@
class="btn btn-update change-icon"
@click="closeEditPane"
>
- {{ $t("nav.next") }}
+ {{ trans(NEXT) }}
@@ -171,10 +171,10 @@
>
- {{ $t(getTextTitle) }}
+ {{ trans(getTextTitle) }}
- {{ $t("loading") }}
+ {{ trans(ADDRESS_LOADING) }}
@@ -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 @@
- {{ $t("fill_an_address") }}
+ {{ trans(ADDRESS_FILL_AN_ADDRESS) }}
@@ -52,10 +52,10 @@
type="text"
name="buildingName"
maxlength="255"
- :placeholder="$t('buildingName')"
+ :placeholder="trans(ADDRESS_BUILDING_NAME)"
v-model="buildingName"
/>
-
+
-
+
-
+