From 533c1792c52f2556b562a2653ea3fbe2643fb60a Mon Sep 17 00:00:00 2001 From: vgreb Date: Thu, 9 Jul 2026 01:35:35 +0200 Subject: [PATCH] =?UTF-8?q?=C3=89ditorialisation=20du=20programme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/packages/backoffice_menu.yaml | 6 + app/config/routing/admin_event.yml | 5 + .../20260708090000_add_position_to_talks.php | 17 ++ .../Event/EventEditorializationAction.php | 150 ++++++++++++++ .../Admin/Event/EventThemeAction.php | 25 +-- .../Event/Model/Repository/TalkRepository.php | 9 +- sources/AppBundle/Event/Model/Talk.php | 13 ++ .../admin/event/editorialization.html.twig | 188 ++++++++++++++++++ templates/admin/event/theme_list.html.twig | 106 ---------- 9 files changed, 387 insertions(+), 132 deletions(-) create mode 100644 db/migrations/20260708090000_add_position_to_talks.php create mode 100644 sources/AppBundle/Controller/Admin/Event/EventEditorializationAction.php create mode 100644 templates/admin/event/editorialization.html.twig diff --git a/app/config/packages/backoffice_menu.yaml b/app/config/packages/backoffice_menu.yaml index d27776f20..4561ab99c 100644 --- a/app/config/packages/backoffice_menu.yaml +++ b/app/config/packages/backoffice_menu.yaml @@ -189,6 +189,12 @@ parameters: - admin_event_themes_list - admin_event_themes_add - admin_event_themes_edit + admin_event_editorialization: + nom: 'Éditorialisation' + niveau: 'ROLE_FORUM' + url: '/admin/event/editorialization' + extra_routes: + - admin_event_editorialization forum_vote_github: nom: 'Votes visiteurs' niveau: 'ROLE_FORUM' diff --git a/app/config/routing/admin_event.yml b/app/config/routing/admin_event.yml index 725b3e9d4..d0e05d914 100644 --- a/app/config/routing/admin_event.yml +++ b/app/config/routing/admin_event.yml @@ -206,5 +206,10 @@ admin_event_themes_list: defaults: _controller: AppBundle\Controller\Admin\Event\EventThemeAction +admin_event_editorialization: + path: /editorialization/ + defaults: + _controller: AppBundle\Controller\Admin\Event\EventEditorializationAction + admin_event_ticket: resource: "admin_event_ticket.yml" diff --git a/db/migrations/20260708090000_add_position_to_talks.php b/db/migrations/20260708090000_add_position_to_talks.php new file mode 100644 index 000000000..9bdfd53be --- /dev/null +++ b/db/migrations/20260708090000_add_position_to_talks.php @@ -0,0 +1,17 @@ +table('afup_sessions') + ->addColumn('position', 'integer', ['null' => true]) + ->save() + ; + } +} diff --git a/sources/AppBundle/Controller/Admin/Event/EventEditorializationAction.php b/sources/AppBundle/Controller/Admin/Event/EventEditorializationAction.php new file mode 100644 index 000000000..ad03ddcf1 --- /dev/null +++ b/sources/AppBundle/Controller/Admin/Event/EventEditorializationAction.php @@ -0,0 +1,150 @@ +event; + + if ($request->isXmlHttpRequest()) { + return $this->handleAjaxRequest($request, $event); + } + + $eventId = $event->getId() ?? 0; + $hasThemes = $event->getHasThemes(); + $themes = $hasThemes ? iterator_to_array($this->eventThemeRepository->getByThemesOrderedByPriority($eventId)) : []; + $scheduledTalks = iterator_to_array($this->talkRepository->getScheduledTalksByEvent($eventId)); + + $talkGroups = $this->groupTalks($scheduledTalks, $themes, $hasThemes); + + return $this->render('admin/event/editorialization.html.twig', [ + 'event' => $event, + 'event_select_form' => $eventSelection->selectForm(), + 'has_themes' => $hasThemes, + 'themes' => $themes, + 'talk_groups' => $talkGroups, + ]); + } + + /** + * @param array $scheduledTalks + * @param array<\AppBundle\Event\Model\EventTheme> $themes + * @return array}> + */ + private function groupTalks(array $scheduledTalks, array $themes, bool $hasThemes): array + { + if (!$hasThemes) { + $talks = $scheduledTalks; + usort($talks, $this->compareTalks(...)); + + return [['theme' => null, 'talks' => $talks]]; + } + + $talksByThemeId = []; + foreach ($themes as $theme) { + $talksByThemeId[(int) $theme->getId()] = []; + } + + $noThemeTalks = []; + foreach ($scheduledTalks as $talk) { + $themeId = $talk->getTheme(); + if ($themeId !== null && isset($talksByThemeId[$themeId])) { + $talksByThemeId[$themeId][] = $talk; + } else { + $noThemeTalks[] = $talk; + } + } + + usort($noThemeTalks, $this->compareTalks(...)); + $groups = [['theme' => null, 'talks' => $noThemeTalks]]; + + foreach ($themes as $theme) { + $talks = $talksByThemeId[(int) $theme->getId()]; + usort($talks, $this->compareTalks(...)); + $groups[] = ['theme' => $theme, 'talks' => $talks]; + } + + return $groups; + } + + private function compareTalks(Talk $a, Talk $b): int + { + $positionA = $a->getPosition(); + $positionB = $b->getPosition(); + + if ($positionA !== $positionB) { + if ($positionA === null) { + return 1; + } + if ($positionB === null) { + return -1; + } + + return $positionA <=> $positionB; + } + + return $a->getTitle() <=> $b->getTitle(); + } + + private function handleAjaxRequest(Request $request, Event $event): JsonResponse + { + $action = $request->request->get('action'); + + return match ($action) { + 'update_talk_theme' => $this->updateTalkTheme($request), + 'update_talk_position' => $this->updateTalkPosition($request), + default => new JsonResponse(['error' => 'Action non reconnue'], 400), + }; + } + + private function updateTalkTheme(Request $request): JsonResponse + { + $talkId = $request->request->getInt('talk_id'); + $themeId = $request->request->get('theme_id'); + + $talk = $this->talkRepository->get($talkId); + if (!$talk) { + return new JsonResponse(['error' => 'Conférence non trouvée'], 404); + } + + $talk->setTheme($themeId ? (int) $themeId : null); + $this->talkRepository->save($talk); + + return new JsonResponse(['success' => true]); + } + + private function updateTalkPosition(Request $request): JsonResponse + { + $talkId = $request->request->getInt('talk_id'); + $position = $request->request->get('position'); + + $talk = $this->talkRepository->get($talkId); + if (!$talk) { + return new JsonResponse(['error' => 'Conférence non trouvée'], 404); + } + + $talk->setPosition($position === null || $position === '' ? null : (int) $position); + $this->talkRepository->save($talk); + + return new JsonResponse(['success' => true]); + } +} diff --git a/sources/AppBundle/Controller/Admin/Event/EventThemeAction.php b/sources/AppBundle/Controller/Admin/Event/EventThemeAction.php index dc901eb35..34f244015 100644 --- a/sources/AppBundle/Controller/Admin/Event/EventThemeAction.php +++ b/sources/AppBundle/Controller/Admin/Event/EventThemeAction.php @@ -7,7 +7,6 @@ use AppBundle\Event\AdminEventSelection; use AppBundle\Event\Model\Event; use AppBundle\Event\Model\Repository\EventThemeRepository; -use AppBundle\Event\Model\Repository\TalkRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -15,10 +14,7 @@ class EventThemeAction extends AbstractController { - public function __construct( - private readonly EventThemeRepository $eventThemeRepository, - private readonly TalkRepository $talkRepository, - ) {} + public function __construct(private readonly EventThemeRepository $eventThemeRepository) {} public function __invoke(Request $request, AdminEventSelection $eventSelection): Response { @@ -42,11 +38,9 @@ public function __invoke(Request $request, AdminEventSelection $eventSelection): $eventId = $event->getId() ?? 0; $themes = $this->eventThemeRepository->getByThemesOrderedByPriority($eventId); - $scheduledTalks = $this->talkRepository->getScheduledTalksByEvent($eventId); return $this->render('admin/event/theme_list.html.twig', [ 'themes' => $themes, - 'scheduled_talks' => $scheduledTalks, 'event' => $event, 'event_select_form' => $eventSelection->selectForm(), ]); @@ -58,7 +52,6 @@ private function handleAjaxRequest(Request $request, Event $event): JsonResponse return match ($action) { 'update_theme_priority' => $this->updateThemePriority($request), - 'update_talk_theme' => $this->updateTalkTheme($request), default => new JsonResponse(['error' => 'Action non reconnue'], 400), }; } @@ -78,20 +71,4 @@ private function updateThemePriority(Request $request): JsonResponse return new JsonResponse(['success' => true]); } - - private function updateTalkTheme(Request $request): JsonResponse - { - $talkId = $request->request->getInt('talk_id'); - $themeId = $request->request->get('theme_id'); - - $talk = $this->talkRepository->get($talkId); - if (!$talk) { - return new JsonResponse(['error' => 'Conférence non trouvée'], 404); - } - - $talk->setTheme($themeId ? (int) $themeId : null); - $this->talkRepository->save($talk); - - return new JsonResponse(['success' => true]); - } } diff --git a/sources/AppBundle/Event/Model/Repository/TalkRepository.php b/sources/AppBundle/Event/Model/Repository/TalkRepository.php index 75e6ca2da..29db1ee8e 100644 --- a/sources/AppBundle/Event/Model/Repository/TalkRepository.php +++ b/sources/AppBundle/Event/Model/Repository/TalkRepository.php @@ -283,7 +283,7 @@ public function getByEventsWithSpeakers(array $events, bool $applyPublicationdat $query = $this->getPreparedQuery( sprintf('SELECT talk.id_forum, talk.session_id, titre, skill, talk.genre, abstract, talk.plannifie, talk.language_code, - talk.joindin, talk.theme, + talk.joindin, talk.theme, talk.position, speaker.conferencier_id, speaker.nom, speaker.prenom, speaker.id_forum, speaker.photo, speaker.societe, planning.id, planning.debut, planning.fin, room.id, room.nom FROM afup_sessions AS talk @@ -293,7 +293,7 @@ public function getByEventsWithSpeakers(array $events, bool $applyPublicationdat LEFT JOIN afup_forum_salle room ON planning.id_salle = room.id LEFT JOIN afup_conference_theme ON afup_conference_theme.id = talk.theme WHERE talk.id_forum IN(%s) AND plannifie = 1 %s %s - ORDER BY %s ', $inEvents, $publicationdateFilters, $themeFilters, $orderByTheme ? 'afup_conference_theme.priority ASC, afup_conference_theme.name ASC' : 'planning.debut ASC, room.id ASC, talk.date_publication DESC, talk.session_id ASC '), + ORDER BY %s ', $inEvents, $publicationdateFilters, $themeFilters, $orderByTheme ? 'afup_conference_theme.priority ASC, afup_conference_theme.name ASC, talk.position IS NULL, talk.position ASC, talk.date_publication DESC, talk.session_id ASC' : 'talk.position IS NULL, talk.position ASC, planning.debut ASC, room.id ASC, talk.date_publication DESC, talk.session_id ASC '), )->setParams($params); $result = $query->query($this->getCollection($hydrator)); @@ -625,6 +625,11 @@ public static function initMetadata(SerializerFactoryInterface $serializerFactor 'fieldName' => 'theme', 'type' => 'int', ]) + ->addField([ + 'columnName' => 'position', + 'fieldName' => 'position', + 'type' => 'int', + ]) ; return $metadata; diff --git a/sources/AppBundle/Event/Model/Talk.php b/sources/AppBundle/Event/Model/Talk.php index 77505f093..13e42a134 100644 --- a/sources/AppBundle/Event/Model/Talk.php +++ b/sources/AppBundle/Event/Model/Talk.php @@ -101,6 +101,8 @@ class Talk implements NotifyPropertyInterface private ?int $theme = null; + private ?int $position = null; + public function __construct() { $this->submittedOn = new \DateTime(); @@ -687,4 +689,15 @@ public function setTheme(?int $theme): void $this->propertyChanged('theme', $this->theme, $theme); $this->theme = $theme; } + + public function getPosition(): ?int + { + return $this->position; + } + + public function setPosition(?int $position): void + { + $this->propertyChanged('position', $this->position, $position); + $this->position = $position; + } } diff --git a/templates/admin/event/editorialization.html.twig b/templates/admin/event/editorialization.html.twig new file mode 100644 index 000000000..f7a5bd99f --- /dev/null +++ b/templates/admin/event/editorialization.html.twig @@ -0,0 +1,188 @@ +{% extends 'admin/base_with_header.html.twig' %} + +{% block content %} +

Éditorialisation du programme

+ + {% include 'admin/event/change_event.html.twig' with {form: event_select_form} only %} + + + +

Position des conférences

+ + {% for group in talk_groups %} +

{{ group.theme ? group.theme.name : 'Sans thème' }}

+ {% if group.talks|length %} + + + + + + {% if has_themes %} + + {% endif %} + + + + + {% for talk in group.talks %} + + + + {% if has_themes %} + + {% endif %} + + + {% endfor %} + +
ConférenceRésuméThèmePosition
+ {{ talk.title }} + + {% set abstract = talk.abstract %} + {% if abstract|length > 400 %} +
+ {{ abstract|slice(0, 400) }}... + +
+ Voir plus +
+ {% else %} +
+ {{ abstract|raw|markdown }} +
+ {% endif %} +
+
+ +
+
+ +
+ {% else %} +
+ + Aucune conférence. +
+ {% endif %} + {% else %} +
+ + Aucune conférence planifiée pour cet événement. +
+ {% endfor %} + + + +{% endblock %} diff --git a/templates/admin/event/theme_list.html.twig b/templates/admin/event/theme_list.html.twig index c1ca07d10..2ee34ddf0 100644 --- a/templates/admin/event/theme_list.html.twig +++ b/templates/admin/event/theme_list.html.twig @@ -72,63 +72,6 @@ -

Association des conférences aux thèmes

-
- {% if scheduled_talks %} - - - - - - - - - - {% for talk in scheduled_talks %} - - - - - - {% endfor %} - -
ConférenceRésuméThème
- {{ talk.title }} - - {% set abstract = talk.abstract %} - {% if abstract|length > 400 %} -
- {{ abstract|slice(0, 400) }}... - -
- Voir plus -
- {% else %} -
- {{ abstract|raw|markdown }} -
- {% endif %} -
-
- -
-
- {% else %} -
- - Aucune conférence planifiée pour cet événement. -
- {% endif %} -
-