diff --git a/.gitignore b/.gitignore index 95850d695..f90ba46d4 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ js_dist drivers chromedriver.log /htdocs/dist +app/config/reference.php diff --git a/db/migrations/20260430000000_add_mic_type_to_speakers.php b/db/migrations/20260430000000_add_mic_type_to_speakers.php new file mode 100644 index 000000000..9f2aa4dbc --- /dev/null +++ b/db/migrations/20260430000000_add_mic_type_to_speakers.php @@ -0,0 +1,13 @@ +query("ALTER TABLE afup_conferenciers ADD mic_type ENUM('handheld', 'headset') DEFAULT NULL AFTER bluesky"); + } +} diff --git a/db/migrations/20260501000000_add_mic_type_enabled_to_forum.php b/db/migrations/20260501000000_add_mic_type_enabled_to_forum.php new file mode 100644 index 000000000..6172ed159 --- /dev/null +++ b/db/migrations/20260501000000_add_mic_type_enabled_to_forum.php @@ -0,0 +1,13 @@ +query("ALTER TABLE afup_forum ADD mic_type_enabled TINYINT DEFAULT 0"); + } +} diff --git a/db/seeds/Event.php b/db/seeds/Event.php index 25cd9fe45..84483512d 100644 --- a/db/seeds/Event.php +++ b/db/seeds/Event.php @@ -50,6 +50,7 @@ public function run(): void 'date_annonce_planning' => $now - $oneMonthInSeconds, 'transport_information_enabled' => 1, 'has_prices_defined_with_vat' => 1, + 'mic_type_enabled' => 1, ], [ 'id' => 2, diff --git a/sources/AppBundle/Event/Form/EventType.php b/sources/AppBundle/Event/Form/EventType.php index cd998b51f..be7773a3f 100644 --- a/sources/AppBundle/Event/Form/EventType.php +++ b/sources/AppBundle/Event/Form/EventType.php @@ -132,6 +132,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => 'Activer les nuits d\'hôtel', 'required' => false, ]) + ->add('micTypeEnabled', CheckboxType::class, [ + 'label' => 'Activer le choix du type de microphone', + 'required' => false, + ]) ->add('coupons', TextareaType::class, [ 'mapped' => false, 'label' => 'Liste des coupons', diff --git a/sources/AppBundle/Event/Model/Event.php b/sources/AppBundle/Event/Model/Event.php index a18e4debd..000d9e885 100644 --- a/sources/AppBundle/Event/Model/Event.php +++ b/sources/AppBundle/Event/Model/Event.php @@ -86,6 +86,8 @@ class Event implements NotifyPropertyInterface private ?bool $accomodationEnabled = null; + private ?bool $micTypeEnabled = null; + private ?bool $transportInformationEnabled = null; /** @@ -625,6 +627,21 @@ public function setAccomodationEnabled($accomodationEnabled): self return $this; } + public function getMicTypeEnabled(): ?bool + { + return $this->micTypeEnabled; + } + + public function setMicTypeEnabled($micTypeEnabled): self + { + $micTypeEnabled = (bool) $micTypeEnabled; + + $this->propertyChanged('micTypeEnabled', $this->micTypeEnabled, $micTypeEnabled); + $this->micTypeEnabled = $micTypeEnabled; + + return $this; + } + public function isAfupDay(): bool { return str_starts_with((string) $this->getTitle(), 'AFUP Day'); diff --git a/sources/AppBundle/Event/Model/Repository/EventRepository.php b/sources/AppBundle/Event/Model/Repository/EventRepository.php index 6fd13e137..00e0343c2 100644 --- a/sources/AppBundle/Event/Model/Repository/EventRepository.php +++ b/sources/AppBundle/Event/Model/Repository/EventRepository.php @@ -475,6 +475,12 @@ public static function initMetadata(SerializerFactoryInterface $serializerFactor 'type' => 'bool', 'serializer' => Boolean::class, ]) + ->addField([ + 'columnName' => 'mic_type_enabled', + 'fieldName' => 'micTypeEnabled', + 'type' => 'bool', + 'serializer' => Boolean::class, + ]) ->addField([ 'columnName' => 'waiting_list_url', 'fieldName' => 'waitingListUrl', diff --git a/sources/AppBundle/Event/Model/Repository/SpeakerRepository.php b/sources/AppBundle/Event/Model/Repository/SpeakerRepository.php index 61027bce9..a84f833b9 100644 --- a/sources/AppBundle/Event/Model/Repository/SpeakerRepository.php +++ b/sources/AppBundle/Event/Model/Repository/SpeakerRepository.php @@ -7,6 +7,7 @@ use AppBundle\Event\Model\Event; use AppBundle\Event\Model\Speaker; use AppBundle\Event\Model\Talk; +use AppBundle\Event\Speaker\MicrophoneType; use AppBundle\Ting\JoinHydrator; use CCMBenchmark\Ting\Driver\Mysqli\Serializer\Boolean; use CCMBenchmark\Ting\Repository\CollectionInterface; @@ -14,6 +15,7 @@ use CCMBenchmark\Ting\Repository\Metadata; use CCMBenchmark\Ting\Repository\MetadataInitializer; use CCMBenchmark\Ting\Repository\Repository; +use CCMBenchmark\Ting\Serializer\BackedEnum; use CCMBenchmark\Ting\Serializer\SerializerFactoryInterface; use Webmozart\Assert\Assert; @@ -63,7 +65,8 @@ public function getScheduledSpeakersByEvent(Event $event, $returnTalksThatWillBe speaker.phone_number, speaker.has_hosting_sponsor, speaker.travel_refund_needed, - speaker.travel_refund_sponsored + speaker.travel_refund_sponsored, + speaker.mic_type FROM afup_conferenciers speaker INNER JOIN afup_conferenciers_sessions cs ON cs.conferencier_id = speaker.conferencier_id INNER JOIN afup_sessions talk ON talk.session_id = cs.session_id @@ -320,6 +323,15 @@ public static function initMetadata(SerializerFactoryInterface $serializerFactor 'type' => 'bool', 'serializer' => Boolean::class, ]) + ->addField([ + 'columnName' => 'mic_type', + 'fieldName' => 'micType', + 'type' => 'enum', + 'serializer' => BackedEnum::class, + 'serializer_options' => [ + 'unserialize' => ['enum' => MicrophoneType::class], + ], + ]) ; return $metadata; diff --git a/sources/AppBundle/Event/Model/Speaker.php b/sources/AppBundle/Event/Model/Speaker.php index 64059a75f..d686aefbf 100644 --- a/sources/AppBundle/Event/Model/Speaker.php +++ b/sources/AppBundle/Event/Model/Speaker.php @@ -4,6 +4,7 @@ namespace AppBundle\Event\Model; +use AppBundle\Event\Speaker\MicrophoneType; use CCMBenchmark\Ting\Entity\NotifyProperty; use CCMBenchmark\Ting\Entity\NotifyPropertyInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -135,6 +136,8 @@ class Speaker implements NotifyPropertyInterface private bool $travelRefundNeeded = true; private bool $travelRefundSponsored = false; + private ?MicrophoneType $micType = null; + public function getId(): ?int { return $this->id; @@ -708,6 +711,17 @@ public function setTravelRefundSponsored(bool $travelRefundSponsored): self return $this; } + public function getMicType(): ?MicrophoneType + { + return $this->micType; + } + + public function setMicType(?MicrophoneType $micType): void + { + $this->propertyChanged('micType', $this->micType, $micType); + $this->micType = $micType; + } + public function hasHotelNightBefore(): ?bool { if (null === ($hotelNights = $this->getHotelNightsArray())) { diff --git a/sources/AppBundle/Event/Speaker/MicrophoneType.php b/sources/AppBundle/Event/Speaker/MicrophoneType.php new file mode 100644 index 000000000..3ec38cfd1 --- /dev/null +++ b/sources/AppBundle/Event/Speaker/MicrophoneType.php @@ -0,0 +1,11 @@ +redirectFromRequest($request); } + $shouldDisplayMicrophoneForm = (bool) $event->getMicTypeEnabled(); + + $speakersMicrophoneType = $this->createForm(SpeakersMicrophoneType::class, ['type' => $speaker->getMicType()]); + $speakersMicrophoneType->handleRequest($request); + if ($shouldDisplayMicrophoneForm && $speakersMicrophoneType->isSubmitted() && $speakersMicrophoneType->isValid()) { + $speaker->setMicType($speakersMicrophoneType->getData()['type']); + $this->speakerRepository->save($speaker); + $this->addFlash('notice', 'Préférence de microphone enregistrée'); + + return $this->redirectFromRequest($request); + } + $speakersDinerDefaults = [ 'will_attend' => $speaker->getWillAttendSpeakersDiner(), 'has_special_diet' => $speaker->getHasSpecialDiet(), @@ -169,6 +182,7 @@ public function handleRequest(Request $request, Event $event, Speaker $speaker) 'description' => $description, 'talks_infos' => $this->addTalkInfos($event, $talks), 'speaker' => $speaker, + 'should_display_microphone_form' => $shouldDisplayMicrophoneForm, 'should_display_speakers_diner_form' => $shouldDisplaySpeakersDinerForm, 'should_display_hotel_reservation_form' => $shouldDisplayHotelReservationForm, 'speakers_expenses_form' => $speakersExpensesType->createView(), @@ -177,6 +191,7 @@ public function handleRequest(Request $request, Event $event, Speaker $speaker) 'speakers_diner_form' => $speakersDinerType->createView(), 'hotel_reservation_form' => $hotelReservationType->createView(), 'speakers_contact_form' => $speakersContactType->createView(), + 'speakers_microphone_form' => $speakersMicrophoneType->createView(), 'day_before_event' => DateTimeImmutable::createFromMutable($event->getDateStart())->modify('- 1 day'), ]); } diff --git a/sources/AppBundle/SpeakerInfos/Form/SpeakersMicrophoneType.php b/sources/AppBundle/SpeakerInfos/Form/SpeakersMicrophoneType.php new file mode 100644 index 000000000..49d6a5e7c --- /dev/null +++ b/sources/AppBundle/SpeakerInfos/Form/SpeakersMicrophoneType.php @@ -0,0 +1,38 @@ +add('type', EnumType::class, [ + 'class' => MicrophoneType::class, + 'label' => 'speaker_infos.microphone.label', + 'expanded' => true, + 'placeholder' => 'speaker_infos.microphone.placeholder', + 'choice_label' => fn(MicrophoneType $type) => match ($type) { + MicrophoneType::Headset => 'speaker_infos.microphone.choice.headset', + MicrophoneType::Handheld => 'speaker_infos.microphone.choice.handheld', + }, + 'choice_translation_domain' => true, + 'required' => false, + ]) + ->add('submit', SubmitType::class, ['label' => 'Enregistrer']); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults(['csrf_protection' => false]); + } +} diff --git a/templates/admin/event/form.html.twig b/templates/admin/event/form.html.twig index d168b87c8..8b59c4b61 100644 --- a/templates/admin/event/form.html.twig +++ b/templates/admin/event/form.html.twig @@ -92,6 +92,7 @@ {{ _self.wysiwyg(form.CFP.speaker_management_en) }} {{ form_row(form.speakersDinerEnabled) }} {{ form_row(form.accomodationEnabled) }} + {{ form_row(form.micTypeEnabled) }} {{ form_row(form.dateEndSpeakersDinerInfosCollection) }} {{ form_row(form.dateEndHotelInfosCollection) }} diff --git a/templates/admin/event/speakers_management.html.twig b/templates/admin/event/speakers_management.html.twig index 5bbf73262..510b1cbfc 100644 --- a/templates/admin/event/speakers_management.html.twig +++ b/templates/admin/event/speakers_management.html.twig @@ -19,6 +19,7 @@ Pas de nuit d'hotel ? Sponsors ? Justificatifs envoyés ? + Microphone @@ -143,6 +144,17 @@ {% endif %} + + + {% if speaker.speaker.micType is null %} + N/A + {% elseif speaker.speaker.micType.value == 'headset' %} + Casque + {% else %} + Main + {% endif %} + + {% else %} - +
Aucune information. {% if event == null %}Essayez de changez d'évènement !{% endif %} diff --git a/templates/event/speaker/page.html.twig b/templates/event/speaker/page.html.twig index 945888fd7..71e4546a2 100644 --- a/templates/event/speaker/page.html.twig +++ b/templates/event/speaker/page.html.twig @@ -13,6 +13,7 @@ {% form_theme speakers_diner_form 'bootstrap_3_layout.html.twig' %} {% form_theme hotel_reservation_form 'bootstrap_3_layout.html.twig' %} {% form_theme travel_sponsor_form 'bootstrap_3_layout.html.twig' %} +{% form_theme speakers_microphone_form 'bootstrap_3_layout.html.twig' %} {% block content %} @@ -121,6 +122,28 @@ {{ form_end(speakers_contact_form) }} + {% if should_display_microphone_form %} +

🎤 {% trans %}speaker_infos.microphone.title{% endtrans %}

+

{% trans %}speaker_infos.microphone.intro{% endtrans %}

+ + {{ form_start(speakers_microphone_form, { attr: { class: "speakers-infos-form" }, action: '#microphone'}) }} + +
+ + {{ form_label(speakers_microphone_form.type) }} + + + {{ form_widget(speakers_microphone_form.type) }} + {{ form_errors(speakers_microphone_form.type) }} + +
+ {{ form_widget(speakers_microphone_form.submit, {attr: {title: "Enregistrer le choix de microphone"}}) }} +
+
+ + {{ form_end(speakers_microphone_form) }} + {% endif %} + {% if event.speakersDinerEnabled %}

🍽️ {% trans %}Nous vous invitons au restaurant{% endtrans %}

diff --git a/tests/behat/features/EventPages/SpeakerInfos.feature b/tests/behat/features/EventPages/SpeakerInfos.feature index 7033947b7..7f0198861 100644 --- a/tests/behat/features/EventPages/SpeakerInfos.feature +++ b/tests/behat/features/EventPages/SpeakerInfos.feature @@ -66,3 +66,23 @@ Feature: Event > Profil speaker And I press "Enregistrer le travel sponsor" Then I should see "Informations sur vos transports enregistrées " Then the "travel_sponsor_choices_1" checkbox should be checked + + @reloadDbWithTestData + Scenario: Saisie du type de microphone - micro casque + Given I go to "/event/forum/speaker-infos" + When I follow "Connect as agallou" + + When I select "headset" from "speakers_microphone[type]" + And I press "Enregistrer le choix de microphone" + Then I should see "Préférence de microphone enregistrée" + Then the "speakers_microphone_type_0" checkbox should be checked + + @reloadDbWithTestData + Scenario: Saisie du type de microphone - micro à main + Given I go to "/event/forum/speaker-infos" + When I follow "Connect as agallou" + + When I select "handheld" from "speakers_microphone[type]" + And I press "Enregistrer le choix de microphone" + Then I should see "Préférence de microphone enregistrée" + Then the "speakers_microphone_type_1" checkbox should be checked diff --git a/translations/messages.en.yml b/translations/messages.en.yml index ba6e7c10f..b66a9aeb4 100644 --- a/translations/messages.en.yml +++ b/translations/messages.en.yml @@ -212,3 +212,9 @@ speaker_infos.hosting_sponsor.intro: "If your hosting is sponsored by your compa speaker_infos.travel_expenses.checkbox_label.not_needed: "I don't need it" speaker_infos.travel_expenses.checkbox_label.sponsored: "My transports costs are sponsored" speaker_infos.sponsor.travel.intro: "Check one of these boxes if you don't need to be refunded your transports costs or if they are are sponsored by your company." +speaker_infos.microphone.title: "Microphone type" +speaker_infos.microphone.intro: "Please indicate your microphone preference for your talks. We will do our best to accommodate your choice depending on availability." +speaker_infos.microphone.label: "Microphone type" +speaker_infos.microphone.placeholder: "No preference" +speaker_infos.microphone.choice.headset: "Headset microphone" +speaker_infos.microphone.choice.handheld: "Handheld microphone" diff --git a/translations/messages.fr.yml b/translations/messages.fr.yml index e27d9d06f..824af5e61 100644 --- a/translations/messages.fr.yml +++ b/translations/messages.fr.yml @@ -236,3 +236,9 @@ speaker_infos.hosting_sponsor.intro: "Si votre hébergement est pris en charge p speaker_infos.travel_expenses.checkbox_label.not_needed: "Je n’en n’ai pas besoin" speaker_infos.travel_expenses.checkbox_label.sponsored: "Mon transport est sponsorisé" speaker_infos.sponsor.travel.intro: "Cochez une de ces cases si vous n'avez pas besoin de défrayment ou si votre transport est pris en charge par votre entreprise." +speaker_infos.microphone.title: "Type de microphone" +speaker_infos.microphone.intro: "Indiquez-nous votre préférence pour le type de microphone lors de vos conférences. Nous ferons notre possible pour respecter votre choix en fonction des disponibilités." +speaker_infos.microphone.label: "Type de microphone" +speaker_infos.microphone.placeholder: "Pas de préférence" +speaker_infos.microphone.choice.headset: "Micro casque" +speaker_infos.microphone.choice.handheld: "Micro à main"