diff --git a/assets/js/planning.js b/assets/js/planning.js index 99871622..2531873f 100644 --- a/assets/js/planning.js +++ b/assets/js/planning.js @@ -19,10 +19,13 @@ function selectTableBox ($tableBox) { colorTableBox($tableBox); } -function initDatesRange($picker, $from, $to, withTime) -{ - function displayDate() { - $picker.val($picker.data('daterangepicker').startDate.format('DD/MM/YYYY HH:mm') + ' à ' + $picker.data('daterangepicker').endDate.format('DD/MM/YYYY HH:mm')); +function initDatesRange ($picker, $from, $to, withTime) { + function displayDate () { + if(withTime) { + $picker.val($picker.data('daterangepicker').startDate.format('DD/MM/YYYY HH:mm') + ' à ' + $picker.data('daterangepicker').endDate.format('DD/MM/YYYY HH:mm')); + } else { + $picker.val($picker.data('daterangepicker').startDate.format('DD/MM/YYYY') + ' au ' + $picker.data('daterangepicker').endDate.format('DD/MM/YYYY')); + } } $picker.daterangepicker({ @@ -35,19 +38,19 @@ function initDatesRange($picker, $from, $to, withTime) cancelClass: 'btn-sm btn-default', locale: { cancelLabel: 'Supprimer', - format: 'DD/MM/YYYY hh:mm', + format: 'DD/MM/YYYY HH:mm', separator: ' - ', applyLabel: 'Valider', fromLabel: 'De', toLabel: 'à', customRangeLabel: 'Custom', - daysOfWeek: [ 'Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam' ], - monthNames: [ 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre' ], + daysOfWeek: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'], + monthNames: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'], firstDay: 1 } }); - if($from.val() !== '' && $to.val() !== '') { + if ($from.val() !== '' && $to.val() !== '') { $picker.data('daterangepicker').setStartDate(new Date($from.val())); $picker.data('daterangepicker').setEndDate(new Date($to.val())); displayDate(); @@ -66,9 +69,9 @@ function initDatesRange($picker, $from, $to, withTime) }); } -function triggerUpdate(url, newStatus, $planning) { +function triggerUpdate (url, newStatus, $planning) { var payload = generatePayload($planning); - if(!Object.keys(payload.assets).length && !Object.keys(payload.users).length) { + if (!Object.keys(payload.assets).length && !Object.keys(payload.users).length) { return; } @@ -83,32 +86,32 @@ function triggerUpdate(url, newStatus, $planning) { updatePlanningFromPayload($planning, newStatus, payload); $('.planning-actions-container .btn').prop('disabled', false); }, - error: function(data) { + error: function (data) { window.alert('Une erreur est survenue, merci de vérifier vos paramètres.'); $('.planning-actions-container .btn').prop('disabled', false); } }); } -function updatePlanningFromPayload($planning, newStatus, payload) { +function updatePlanningFromPayload ($planning, newStatus, payload) { ['users', 'assets'].forEach(ownerType => { var currentObjects = payload[ownerType] || {}; Object.keys(currentObjects).forEach(objectId => { - payload[ownerType][objectId].forEach(schedule => { - var [from,to] = schedule; - $td = $planning.find('tr[data-type="'+ownerType+'"][data-id="'+objectId+'"] td[data-from="'+from+'"][data-to="'+to+'"]'); - $td - .removeClass($td.data('status')) - .addClass(newStatus) - .data('status', newStatus); - }); + payload[ownerType][objectId].forEach(schedule => { + var [from, to] = schedule; + $td = $planning.find('tr[data-type="' + ownerType + '"][data-id="' + objectId + '"] td[data-from="' + from + '"][data-to="' + to + '"]'); + $td + .removeClass($td.data('status')) + .addClass(newStatus) + .data('status', newStatus); + }); }); }); $planning.find('.checked').removeClass('checked').find('input:checkbox').prop('checked', false); } -function generatePayload($planning) { +function generatePayload ($planning) { var payload = { users: {}, assets: {} @@ -120,7 +123,7 @@ function generatePayload($planning) { var type = $owner.data('type'); var $parent = $(this).closest('td'); - if(!payload[type][ownerId]) { + if (!payload[type][ownerId]) { payload[type][ownerId] = []; } payload[type][ownerId].push([$parent.data('from'), $parent.data('to')]); @@ -146,10 +149,10 @@ $(document).ready(function () { triggerUpdate($(this).data('href'), $(this).data('status'), $planning); }); - $planning.find('input[type=checkbox]:checked').closest('.slot-box').addClass('checked'); - // Datepickers initDatesRange($('#fromToRange'), $('#from'), $('#to')); initDatesRange($('#availableRange'), $('#availableFrom'), $('#availableTo'), true); + + $planning.find('input[type=checkbox]:checked').closest('.slot-box').addClass('checked'); }); diff --git a/src/Controller/Organization/PlanningController.php b/src/Controller/Organization/PlanningController.php index fed3c74c..622155d3 100644 --- a/src/Controller/Organization/PlanningController.php +++ b/src/Controller/Organization/PlanningController.php @@ -38,29 +38,30 @@ public function __construct(UserRepository $userRepository, CommissionableAssetR public function __invoke(Request $request): Response { - $data = [ - 'from' => new \DateTimeImmutable('monday'), - 'to' => (new \DateTimeImmutable('monday'))->add(new \DateInterval('P1W')), - 'volunteer' => true, - 'volunteerEquipped' => true, - 'volunteerHideVulnerable' => true, - 'asset' => true, - ]; - - $form = $this->container->get('form.factory')->createNamed('', PlanningSearchType::class, $data, ['method' => 'GET', 'attr' => ['autocomplete' => 'off']]); - $form->handleRequest($request); + if (!$request->query->has('from')) { + $request->query->set('from', (new \DateTimeImmutable('monday this week'))->format('Y-m-d\T00:00:00')); + } - $from = $form->get('from')->getData(); - $to = $form->get('to')->getData(); + if (!$request->query->has('to')) { + $from = new \DateTimeImmutable($request->query->get('from', 'monday this week')); + $request->query->set('to', $from->add(new \DateInterval('P1W'))->format('Y-m-d\T00:00:00')); + } - $periodCalculator = DatePeriodCalculator::createRoundedToDay($from, new \DateInterval('PT2H'), $to); + $form = $this->container->get('form.factory')->createNamed('', PlanningSearchType::class, [], ['method' => 'GET', 'attr' => ['autocomplete' => 'off']]); + $form->handleRequest($request); - if ($form->isSubmitted() && $form->isValid()) { - [$users, $assets] = $this->searchEntities($form->getData()); - $usersAvailabilities = $this->prepareAvailabilities($this->userAvailabilityRepository, $users, $periodCalculator); - $assetsAvailabilities = $this->prepareAvailabilities($this->assetAvailabilityRepository, $assets, $periodCalculator); + $data = $form->getData(); + if (!isset($data['from'], $data['to'])) { + // This may happen if the passed date is invalid. TODO check it before, the format must be 2020-03-30T00:00:00 + throw $this->createNotFoundException(); } + $periodCalculator = DatePeriodCalculator::createRoundedToDay($data['from'], new \DateInterval('PT2H'), $data['to']); + + [$users, $assets] = $this->searchEntities($data); + $usersAvailabilities = $this->prepareAvailabilities($this->userAvailabilityRepository, $users, $periodCalculator); + $assetsAvailabilities = $this->prepareAvailabilities($this->assetAvailabilityRepository, $assets, $periodCalculator); + return $this->render('organization/planning.html.twig', [ 'form' => $form->createView(), 'periodCalculator' => $periodCalculator, @@ -71,8 +72,8 @@ public function __invoke(Request $request): Response private function searchEntities(array $formData): array { - $users = $this->userRepository->findByFilters($formData); - $assets = $this->assetRepository->findByFilters($formData); + $users = $formData['hideUsers'] ?? false ? [] : $this->userRepository->findByFilters($formData); + $assets = $formData['hideAssets'] ?? false ? [] : $this->assetRepository->findByFilters($formData); return [$users, $assets]; } diff --git a/src/Form/Type/PlanningSearchType.php b/src/Form/Type/PlanningSearchType.php index 8be1df34..a94d4348 100644 --- a/src/Form/Type/PlanningSearchType.php +++ b/src/Form/Type/PlanningSearchType.php @@ -12,10 +12,8 @@ use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\FormEvents; +use Symfony\Component\OptionsResolver\OptionsResolver; class PlanningSearchType extends AbstractType { @@ -64,27 +62,27 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'attr' => ['class' => 'selectpicker'], ]) - ->add('volunteer', CheckboxType::class, [ - 'label' => 'Bénévoles', + ->add('hideUsers', CheckboxType::class, [ + 'label' => 'Cacher les bénévoles', 'required' => false, ]) - ->add('volunteerSkills', ChoiceType::class, [ + ->add('userSkills', ChoiceType::class, [ 'label' => 'Compétences', 'choices' => array_flip($this->availableSkillSets), 'multiple' => true, 'required' => false, 'attr' => ['class' => 'selectpicker'], ]) - ->add('volunteerEquipped', CheckboxType::class, [ + ->add('onlyFullyEquiped', CheckboxType::class, [ 'label' => 'Avec uniforme seulement', 'required' => false, ]) - ->add('volunteerHideVulnerable', CheckboxType::class, [ - 'label' => 'Cacher les personnes signalées comme vulnérables', + ->add('displayVulnerables', CheckboxType::class, [ + 'label' => 'Afficher aussi les personnes signalées comme vulnérables', 'required' => false, ]) - ->add('asset', CheckboxType::class, [ - 'label' => 'Véhicules', + ->add('hideAssets', CheckboxType::class, [ + 'label' => 'Cacher les véhicules', 'required' => false, ]) ->add('assetTypes', ChoiceType::class, [ @@ -94,15 +92,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'attr' => ['class' => 'selectpicker'], ]) - ->add('submit', SubmitType::class, ['label' => 'Filtrer']) ; + } - // Cannot use contraint in upper types, because it's not bound to an entity (therefore PropertyAccessor cannot succeed) - $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) { - $data = $event->getData() ?? []; - if (array_key_exists('from', $data) && array_key_exists('to', $data) && $data['from'] >= $data['to']) { - throw new \InvalidArgumentException('Invalid payload'); // TODO Put a better error - } - }); + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'csrf_protection' => false, + ]); } } diff --git a/src/Repository/CommissionableAssetRepository.php b/src/Repository/CommissionableAssetRepository.php index 229b0a78..38b31d2e 100644 --- a/src/Repository/CommissionableAssetRepository.php +++ b/src/Repository/CommissionableAssetRepository.php @@ -36,11 +36,11 @@ public function findByFilters(array $formData) { $qb = $this->createQueryBuilder('a'); - if (0 < count($formData['assetTypes'])) { + if (count($formData['assetTypes'] ?? []) > 0) { $qb->andWhere('a.type IN (:types)')->setParameter('types', $formData['assetTypes']); } - if (0 < $formData['organizations']->count()) { + if (count($formData['organizations'] ?? []) > 0) { $qb->andWhere('a.organization IN (:organisations)')->setParameter('organisations', $formData['organizations']); } diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index cf0e00d6..56f54988 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -51,27 +51,21 @@ public function findByFilters(array $formData) { $qb = $this->createQueryBuilder('u'); - $skillsQueries = []; - foreach (array_values($formData['volunteerSkills']) as $key => $skill) { - $skillsQueries[] = sprintf('CONTAINS(u.skillSet, ARRAY(:skill%d)) = TRUE', $key); - $qb->setParameter(sprintf('skill%d', $key), $skill); - } - - if (0 < $formData['organizations']->count()) { + if (count($formData['organizations'] ?? []) > 0) { $qb->andWhere('u.organization IN (:organisations)')->setParameter('organisations', $formData['organizations']); } - if ($formData['volunteerEquipped']) { + if ($formData['onlyFullyEquiped'] ?? false) { $qb->andWhere('u.fullyEquipped = TRUE'); } - if ($formData['volunteerHideVulnerable']) { + if ($formData['displayVulnerables'] ?? false) { $qb->andWhere('u.vulnerable = FALSE'); } - if (0 < count($formData['volunteerSkills'])) { + if (count($formData['userSkills'] ?? []) > 0) { $skillsQueries = []; - foreach (array_values($formData['volunteerSkills']) as $key => $skill) { + foreach (array_values($formData['userSkills']) as $key => $skill) { $skillsQueries[] = sprintf('CONTAINS(u.skillSet, ARRAY(:skill%d)) = TRUE', $key); $qb->setParameter(sprintf('skill%d', $key), $skill); } diff --git a/templates/organization/home.html.twig b/templates/organization/home.html.twig index d68bee2a..8fd32d33 100644 --- a/templates/organization/home.html.twig +++ b/templates/organization/home.html.twig @@ -8,10 +8,10 @@

{{ app.user }}

Semaine actuelle : du {{ 'this week' | date('d/m/Y') }} au {{ 'sunday this week' | date('d/m/Y') }}

-

+

Afficher les disponibilités de mes bénévoles pour la semaine actuelle

Semaine prochaine : du {{ 'next week' | date('d/m/Y') }} au {{ 'sunday next week' | date('d/m/Y') }}

-

+

Afficher les disponibilités de mes bénévoles pour la semaine prochaine


diff --git a/templates/organization/planning/_search_type.html.twig b/templates/organization/planning/_search_type.html.twig index f2c14b30..eea864e1 100644 --- a/templates/organization/planning/_search_type.html.twig +++ b/templates/organization/planning/_search_type.html.twig @@ -1,5 +1,5 @@
- {{ form_row(form.volunteer) }} + {{ form_row(form.hideUsers) }}
- {{ form_label(form.volunteerSkills, null, {'label_attr': {'class': 'col-12 col-md-3'}}) }} + {{ form_label(form.userSkills, null, {'label_attr': {'class': 'col-12 col-md-3'}}) }}
- {{ form_widget(form.volunteerSkills) }} + {{ form_widget(form.userSkills) }}
- {{ form_row(form.volunteerEquipped) }} + {{ form_row(form.onlyFullyEquiped) }}
- {{ form_row(form.asset) }} + {{ form_row(form.hideAssets) }}
{{ form_label(form.assetTypes, null, {'label_attr': {'class': 'col-12 col-md-3'}}) }} @@ -57,6 +57,9 @@ {{ form_widget(form.assetTypes) }}
+
+ +
{{ form_end(form) }}