From dd64950b5794f3dfb73bbeb9897cc85e2b250387 Mon Sep 17 00:00:00 2001
From: pfleu <fleutotp@gmail.com>
Date: Wed, 24 May 2023 16:25:17 +0200
Subject: [PATCH] =?UTF-8?q?D=C3=A9but=20Page=20label?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 public/assets/css/app.css                     |   8 +
 src/Controller/LabelController.php            | 227 +++++++++++++++---
 src/Controller/LexiconController.php          |   3 +
 src/Entity/Label.php                          |  20 +-
 src/Form/CopyHeadwordsType.php                |  65 +++++
 src/Form/EnterWordType.php                    |  48 ++++
 src/Form/LabelDescriptionType.php             |  48 ++++
 src/Form/LabelType.php                        |   9 +
 src/Repository/HeadwordRepository.php         |  47 ++++
 src/Twig/AppTwigExtension.php                 |   4 +-
 templates/label/_labelBadge.html.twig         |   3 +-
 templates/label/_labelLine.html.twig          |   4 +-
 templates/label/_labelsGeneral.html.twig      |   6 +-
 .../label/_labelsInstitutional.html.twig      |   2 +-
 templates/label/_labelsMilestone.html.twig    |   4 +-
 templates/label/_selectionActions.html.twig   |  15 ++
 templates/label/show.html.twig                | 183 ++++++++++++++
 templates/lexicon/show.html.twig              |  19 +-
 18 files changed, 666 insertions(+), 49 deletions(-)
 create mode 100644 src/Form/CopyHeadwordsType.php
 create mode 100644 src/Form/EnterWordType.php
 create mode 100644 src/Form/LabelDescriptionType.php
 create mode 100644 templates/label/_selectionActions.html.twig
 create mode 100644 templates/label/show.html.twig

diff --git a/public/assets/css/app.css b/public/assets/css/app.css
index 1b3b5a1..b8783aa 100644
--- a/public/assets/css/app.css
+++ b/public/assets/css/app.css
@@ -36,6 +36,7 @@
 
 body a {
     text-decoration: none;
+    color: inherit;
 }
 
 table.py-sm > tbody > tr > td, table.py-sm > tbody > tr > th {
@@ -137,6 +138,13 @@ table.label-table > tbody > tr > td {
     /*vertical-align: top;*/
 }
 
+.table >tbody>tr>td.bg-grey, .table >thead>tr>th.bg-grey, .bg-grey {
+    background-color: #eeeeee;
+}
+.table >tbody>tr>td.bg-green, .table >thead>tr>th.bg-green, .bg-green {
+    background-color: #b5e0b5;
+}
+
 .bg-label-personal {
     background-color: #d9f2ff;
 }
diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php
index e1c815c..2214d65 100644
--- a/src/Controller/LabelController.php
+++ b/src/Controller/LabelController.php
@@ -4,13 +4,18 @@ namespace App\Controller;
 
 use App\Entity\Entry;
 use App\Entity\Group;
+use App\Entity\Headword;
 use App\Entity\Label;
 use App\Entity\LabelVisibility;
 use App\Entity\Lexicon;
 use App\Entity\Log;
+use App\Form\CopyEntriesType;
+use App\Form\CopyHeadwordsType;
+use App\Form\LabelDescriptionType;
 use App\Form\LabelType;
 use App\Form\SearchStringType;
 use App\Manager\LabelManager;
+use App\Repository\HeadwordRepository;
 use App\Repository\LabelRepository;
 use Doctrine\Persistence\ManagerRegistry;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
@@ -24,7 +29,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
 /**
  * @Route("/label")
  */
-class LabelController extends AbstractController
+class LabelController extends AppBaseController
 {
     /**
      * @Route("/", name="app_label_index", methods={"GET"})
@@ -63,20 +68,19 @@ class LabelController extends AbstractController
             $label->setGroup($group);
         }
         $form = $this->createForm(LabelType::class, $label, [
-//            'user' => $this->getUser(),
+            'action' => $this->generateUrl('app_label_new', ['masterType' => $masterType, 'category' => $category])
         ]);
         $form->handleRequest($request);
 
         if ($form->isSubmitted() && $form->isValid()) {
             $labelRepository->add($label, true);
 
-            return $this->redirectToRoute('app_label_index');
+            return $this->render('closeModalAndReload.html.twig');
         }
 
-        return $this->render('genericForm.html.twig', [
+        return $this->render('genericModalForm.html.twig', [
             'title' => "Créer un label",
             'form' => $form->createView(),
-            'back_url' => $this->generateUrl('app_label_index'),
         ]);
     }
 
@@ -84,46 +88,201 @@ class LabelController extends AbstractController
      * @Route("/{id}", name="app_label_show", methods={"GET"})
      * @IsGranted("LABEL_VIEW", subject="label")
      */
-    public function show(Label $label): Response
+    public function show(Request $request, Label $label): Response
     {
+        $sortingColumn = null;
+        $sortingOrder = null;
+        $this->getSortingParameters($request, $sortingColumn, $sortingOrder, 'label_show');
+
+        $form = $this->createForm(SearchStringType::class,null, array('method' => 'GET'));
+        $form->handleRequest($request);
+        $filter = $form->getData();
+
+        $filter['label'] = $label;
+
+//        // Entrées triées par createdAt
+//        // On ajoute l'ordre d'ajout de l'entrée (on se base sur createdAt) comme index
+//        $entriesOrdered = $this->em->getRepository(Entry::class)->filter($filter, 'createdAt', 'ASC');
+//        foreach ($entriesOrdered as $key => $entryOrdered) {
+//            $entryOrdered->setAddingOrder($key);
+//        }
+//        // On ajoute l'id de l'entrée en index ( => tableau assciatif 'id'  => $entry (avec colonne addingOrder)
+//        $entriesOrderedIndexedById = [];
+//        foreach ($entriesOrdered as $entryOrdered) {
+//            $entriesOrderedIndexedById[$entryOrdered->getId()] = $entryOrdered;
+//        }
+//
+//        // Entrées résultat cherché (triées avec le filtre sortingColumn)
+//        // On ajoute l'id de l'entrée en index ( => tableau assciatif 'id'  => $entry (avec colonne addingOrder)
+//        $entries = $this->em->getRepository(Entry::class)->filter($filter, $sortingColumn, $sortingOrder);
+//        $entriesIndexedById = [];
+//        foreach ($entries as $entry) {
+//            $entriesIndexedById[$entry->getId()] = $entry;
+//        }
+//        // On ajoute l'ordre d'ajout de l'entrée à partir du premier tableau
+//        foreach ($entriesIndexedById as $id => $entryIndexedById) {
+//            $entryIndexedById->setAddingOrder($entriesOrderedIndexedById[$id]->getAddingOrder());
+//        }
+
+        // On affiche tous les headwords possédant le label
+        $filter['label'] = $label;
+        $headwords = $this->em->getRepository(Headword::class)->filter($filter, $sortingColumn, $sortingOrder);
+
+        if ($request->get('shuffle') && !$request->get('sortingColumn')) {
+            shuffle($headwords);
+        }
+//        $entriesWithAddingOrderIndex = $entriesOrdered;
+//        usort($entriesWithAddingOrderIndex, function ($a, $b) { return $b->getCreatedAt()->getTimestamp() - $a->getCreatedAt()->getTimestamp(); });
+
         return $this->render('label/show.html.twig', [
-            'label' => $label,
+            'headwords'         => $headwords,
+            'label'             => $label,
+            'form'              => $form->createView(),
+            'sortingColumn'     => $sortingColumn,
+            'sortingOrder'      => $sortingOrder,
+        ]);
+    }
+
+    /**
+     * @Route("/{id}/process-selected-entries", name="app_label_process_selected_headwords")
+     */
+    public function processSelectedEntries(Request $request, HeadwordRepository $headwordRepository, Label $label)
+    {
+        $formData = $request->get('form');
+        $selectedIds = $formData['selected_headwords'] ?? null;
+
+        if ($selectedIds) {
+
+            if ($request->request->has('remove_label_from_headwords')) {
+                foreach ($selectedIds as $selectedId) {
+                    $headword = $headwordRepository->find($selectedId);
+                    $headword->removeLabel($label);
+                }
+                $this->em->flush();
+            }
+        }
+
+        return $this->redirectToRoute('app_label_show', array(
+            'id' => $label->getId(),
+        ));
+    }
+    /**
+     * @Route("/{id}/copy-entries", name="app_label_copy_headwords")
+     */
+    public function copySelection(Request $request, Label $label, HeadwordRepository $headwordRepository)
+    {
+        $formData = $request->get('form');
+        $selectedIds = $formData['selected_headwords'] ?? null;
+
+        $form = $this->createForm(CopyHeadwordsType::class, null, [
+            'action' => $this->generateUrl('app_label_copy_headwords', [
+                'id' => $label->getId(),
+                'selectedIds' => $selectedIds,
+            ]),
+        ]);
+        $form->handleRequest($request);
+
+        if ($form->isSubmitted() && $form->isValid()) {
+            $selectedIds = $request->get('selectedIds');
+            $lexicon = $form->get('lexicon')->getData();
+            foreach ($selectedIds as $selectedId) {
+                $headword = $headwordRepository->find($selectedId);
+                if (!$lexicon->getEntryForHeadword($headword)) {
+                    $this->createEntryInLexicon($headword, $lexicon);
+                }
+            }
+            $this->em->flush();
+            $this->addFlash('success', "Copie effectuée");
+
+            return $this->render('closeModalAndReload.html.twig');
+        }
+
+        return $this->render('genericModalForm.html.twig', [
+            'title' => "Copie des mots-vedettes vers un autre lexique",
+            'form' => $form->createView(),
         ]);
     }
 
 
-//    /**
-//     * @Route("/{id}/edit", name="app_label_edit", methods={"GET", "POST"})
+    /**
+     * @Route("/{id}/edit", name="app_label_edit", methods={"GET", "POST"})
+     */
 //     * @IsGranted("LABEL_EDIT", subject="label")
-//     */
-//    public function edit(Request $request, Label $label, LabelRepository $labelRepository): Response
-//    {
-//        $form = $this->createForm(LabelType::class, $label);
-//        $form->handleRequest($request);
-//
-//        if ($form->isSubmitted() && $form->isValid()) {
-//            $labelRepository->add($label, true);
-//
-//            return $this->redirectToRoute('app_label_index');
-//        }
-//
-//        return $this->render('genericForm.html.twig', [
-//            'title' => "Modifier un labele",
-//            'form' => $form->createView(),
-//            'back_url' => $this->generateUrl('app_label_show', ['id' => $label->getId()]),
-//        ]);
-//    }
+    public function edit(Request $request, Label $label, LabelRepository $labelRepository): Response
+    {
+        $form = $this->createForm(LabelType::class, $label, [
+            'action' => $this->generateUrl('app_label_edit', ['id' => $label->getId()])
+        ]);
+        $form->handleRequest($request);
+
+        if ($form->isSubmitted() && $form->isValid()) {
+            $labelRepository->add($label, true);
+
+            return $this->render('closeModalAndReload.html.twig');
+        }
+
+        return $this->render('genericModalForm.html.twig', [
+            'title' => "Modifier le label",
+            'form' => $form->createView(),
+        ]);
+    }
+
 
     /**
-     * @Route("/{id}/delete", name="app_label_delete", methods={"POST"})
-     * @IsGranted("LABEL_DELETE", subject="label")
+     * @Route("/{id}/edit-description", name="app_label_edit_description", methods={"GET", "POST"})
      */
-    public function delete(Request $request, Label $label, LabelRepository $labelRepository): Response
+//     * @IsGranted("LABEL_EDIT", subject="label")
+    public function editDescription(Request $request, Label $label, LabelRepository $labelRepository): Response
+    {
+        $form = $this->createForm(LabelDescriptionType::class, $label, [
+            'action' => $this->generateUrl('app_label_edit_description', ['id' => $label->getId()])
+        ]);
+        $form->handleRequest($request);
+
+        if ($form->isSubmitted() && $form->isValid()) {
+            $labelRepository->add($label, true);
+
+            return $this->render('closeModalAndReload.html.twig');
+        }
+
+        return $this->render('genericModalForm.html.twig', [
+            'title' => "Modifier le label",
+            'form' => $form->createView(),
+        ]);
+    }
+
+
+    /**
+     * @Route("/{id}/add-headword", name="app_label_add_headword", methods={"GET", "POST"})
+     */
+//     * @IsGranted("LABEL_EDIT", subject="label")
+    public function addHeadwordDescription(Request $request, Label $label): Response
     {
-        if ($this->isCsrfTokenValid('delete'.$label->getId(), $request->request->get('_token'))) {
-            $labelRepository->remove($label, true);
+        $form = $this->createForm(LabelDescriptionType::class, $label, [
+            'action' => $this->generateUrl('app_label_add_headword', ['id' => $label->getId()])
+        ]);
+        $form->handleRequest($request);
+
+        if ($form->isSubmitted() && $form->isValid()) {
+            $labelRepository->add($label, true);
+
+            return $this->render('closeModalAndReload.html.twig');
         }
 
+        return $this->render('genericModalForm.html.twig', [
+            'title' => "Modifier le label",
+            'form' => $form->createView(),
+        ]);
+    }
+
+    /**
+     * @Route("/{id}/delete", name="app_label_delete")
+     */
+//     * @IsGranted("LABEL_DELETE", subject="label")
+    public function delete(Request $request, Label $label, LabelRepository $labelRepository): Response
+    {
+        $labelRepository->remove($label, true);
+
         return $this->redirectToRoute('app_label_index');
     }
 
@@ -155,6 +314,10 @@ class LabelController extends AbstractController
 
         $doctrine->getManager()->flush();
 
+        if ($backUrl = $request->get('backUrl')) {
+            return $this->redirect($backUrl);
+        }
+
         return $this->redirectToRoute('app_label_index', ['showVisibility' => $label->getCategory()]);
     }
 
diff --git a/src/Controller/LexiconController.php b/src/Controller/LexiconController.php
index c4ee8e0..07f334d 100644
--- a/src/Controller/LexiconController.php
+++ b/src/Controller/LexiconController.php
@@ -64,6 +64,9 @@ class LexiconController extends AppBaseController
             $entryIndexedById->setAddingOrder($entriesOrderedIndexedById[$id]->getAddingOrder());
         }
 
+        if ($request->get('shuffle') && !$request->get('sortingColumn')) {
+            shuffle($entriesIndexedById);
+        }
 //        $entriesWithAddingOrderIndex = $entriesOrdered;
 //        usort($entriesWithAddingOrderIndex, function ($a, $b) { return $b->getCreatedAt()->getTimestamp() - $a->getCreatedAt()->getTimestamp(); });
 
diff --git a/src/Entity/Label.php b/src/Entity/Label.php
index 87bfe29..e3ae077 100644
--- a/src/Entity/Label.php
+++ b/src/Entity/Label.php
@@ -69,6 +69,12 @@ class Label
      */
     private $name;
 
+    /**
+     * @ORM\Column(type="text", nullable=true)
+     * @Groups({"label:read"})
+     */
+    private $description;
+
     /**
      * @ORM\Column(type="string", length=80)
      * @Groups({"label:read"})
@@ -89,7 +95,7 @@ class Label
     private $source;
 
     /**
-     * @ORM\Column(type="datetime", nullable=true)
+     * @ORM\Column(type="datetime_immutable", nullable=true)
      * @Groups({"label:read"})
      */
     private $milestone;
@@ -444,4 +450,16 @@ class Label
 
         return $this;
     }
+
+    public function getDescription(): ?string
+    {
+        return $this->description;
+    }
+
+    public function setDescription(?string $description): self
+    {
+        $this->description = $description;
+
+        return $this;
+    }
 }
diff --git a/src/Form/CopyHeadwordsType.php b/src/Form/CopyHeadwordsType.php
new file mode 100644
index 0000000..62e7548
--- /dev/null
+++ b/src/Form/CopyHeadwordsType.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace App\Form;
+
+use App\Entity\Group;
+use App\Entity\Lexicon;
+use App\Entity\User;
+use App\Languages\LanguagesIso;
+use App\Repository\LexiconRepository;
+use Symfony\Bridge\Doctrine\Form\Type\EntityType;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Security\Core\Security;
+use Symfony\Component\Validator\Constraints\NotBlank;
+
+class CopyHeadwordsType extends AbstractType
+{
+    /**
+     * @var Security
+     */
+    protected $security;
+
+    public function __construct(Security $security)
+    {
+        $this->security = $security;
+    }
+
+    public function buildForm(FormBuilderInterface $builder, array $options): void
+    {
+        $user = $this->security->getUser();
+        $lexicons = $user->getMyLexicons();
+
+        $builder
+            ->add('lexicon', EntityType::class, [
+                'class' => Lexicon::class,
+                'constraints' => [
+                    new NotBlank([
+                        'message' => 'Veuillez choisir un lexique de destination',
+                    ]),
+                ],
+                'label' => 'Lexique de destination',
+                'choices' => $lexicons,
+            ])
+            ->add('submit', SubmitType::class, [
+                'label' => 'Enregistrer',
+                'attr' => ['class' => 'btn-dark']
+            ])
+        ;
+    }
+
+    public function configureOptions(OptionsResolver $resolver): void
+    {
+        $resolver->setDefaults([
+            'attr' => [
+                'data-ajax-form' => '',
+                'data-ajax-form-target' => '#bootstrap-modal .modal-content',
+                'novalidate' => 'novalidate',
+            ],
+        ]);
+    }
+}
diff --git a/src/Form/EnterWordType.php b/src/Form/EnterWordType.php
new file mode 100644
index 0000000..ec15200
--- /dev/null
+++ b/src/Form/EnterWordType.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Form;
+
+use App\Entity\Label;
+use App\Entity\User;
+use App\Languages\LanguagesIso;
+use App\Repository\GroupRepository;
+use Symfony\Bridge\Doctrine\Form\Type\EntityType;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Form\Extension\Core\Type\TextareaType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+class EnterWordType extends AbstractType
+{
+    public function buildForm(FormBuilderInterface $builder, array $options): void
+    {
+
+        $builder
+            ->add('description', TextareaType::class, [
+                'label' => 'Description',
+            ])
+        ;
+
+        $builder
+            ->add('submit', SubmitType::class, [
+                'label' => 'Enregistrer',
+            ]);
+    }
+
+    public function configureOptions(OptionsResolver $resolver): void
+    {
+        $resolver->setDefaults([
+            'data_class' => Label::class,
+            'attr' => [
+                'data-ajax-form' => '',
+                'data-ajax-form-target' => '#bootstrap-modal .modal-content',
+                'novalidate' => 'novalidate',
+            ],
+        ]);
+    }
+}
diff --git a/src/Form/LabelDescriptionType.php b/src/Form/LabelDescriptionType.php
new file mode 100644
index 0000000..17cce69
--- /dev/null
+++ b/src/Form/LabelDescriptionType.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Form;
+
+use App\Entity\Label;
+use App\Entity\User;
+use App\Languages\LanguagesIso;
+use App\Repository\GroupRepository;
+use Symfony\Bridge\Doctrine\Form\Type\EntityType;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Form\Extension\Core\Type\TextareaType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+class LabelDescriptionType extends AbstractType
+{
+    public function buildForm(FormBuilderInterface $builder, array $options): void
+    {
+
+        $builder
+            ->add('description', TextareaType::class, [
+                'label' => 'Description',
+            ])
+        ;
+
+        $builder
+            ->add('submit', SubmitType::class, [
+                'label' => 'Enregistrer',
+            ]);
+    }
+
+    public function configureOptions(OptionsResolver $resolver): void
+    {
+        $resolver->setDefaults([
+            'data_class' => Label::class,
+            'attr' => [
+                'data-ajax-form' => '',
+                'data-ajax-form-target' => '#bootstrap-modal .modal-content',
+                'novalidate' => 'novalidate',
+            ],
+        ]);
+    }
+}
diff --git a/src/Form/LabelType.php b/src/Form/LabelType.php
index 38d3f67..bb539c0 100644
--- a/src/Form/LabelType.php
+++ b/src/Form/LabelType.php
@@ -12,6 +12,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
 use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
 use Symfony\Component\Form\Extension\Core\Type\DateType;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Form\Extension\Core\Type\TextareaType;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
 use Symfony\Component\Form\FormBuilderInterface;
 use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -27,6 +28,9 @@ class LabelType extends AbstractType
             ->add('name', TextType::class, [
                 'label' => 'Nom',
             ])
+            ->add('description', TextareaType::class, [
+                'label' => 'Description',
+            ])
         ;
 
 //        if ($label->isMasterGroup()) {
@@ -67,6 +71,11 @@ class LabelType extends AbstractType
         $resolver->setDefaults([
             'data_class' => Label::class,
             'user' => null,
+            'attr' => [
+                'data-ajax-form' => '',
+                'data-ajax-form-target' => '#bootstrap-modal .modal-content',
+                'novalidate' => 'novalidate',
+            ],
         ]);
     }
 }
diff --git a/src/Repository/HeadwordRepository.php b/src/Repository/HeadwordRepository.php
index db9d520..4a6b553 100644
--- a/src/Repository/HeadwordRepository.php
+++ b/src/Repository/HeadwordRepository.php
@@ -6,6 +6,7 @@ use App\Entity\Headword;
 use App\Entity\Label;
 use App\Entity\User;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+use Doctrine\ORM\QueryBuilder;
 use Doctrine\Persistence\ManagerRegistry;
 
 /**
@@ -23,6 +24,52 @@ class HeadwordRepository extends ServiceEntityRepository
         parent::__construct($registry, Headword::class);
     }
 
+    /**
+     * @return Headword[] Returns an array of Entry objects
+     */
+    public function filter($filter, $sortingColumn = null, $sortingOrder = null): array
+    {
+        $qb = $this->createQueryBuilder('h')
+            ->leftJoin('h.labels', 'lb')
+        ;
+
+        if ($filter['label'] ?? null) {
+            $qb->andWhere('lb = :label')
+                ->setParameter('label', $filter['label'])
+            ;
+        }
+
+        if (!empty($filter['searchString'])) {
+            $qb ->andWhere('h.value LIKE :searchString')
+                ->setParameter('searchString', '%' . $filter['searchString'] . '%');
+        }
+
+        if ($sortingColumn) {
+            $qb = $this->setSortingOrder($qb, $sortingColumn, $sortingOrder);
+        } else {
+            $qb->orderBy('h.createdAt', 'ASC');
+        }
+
+        return $qb
+            ->getQuery()
+            ->getResult()
+            ;
+    }
+
+    private function setSortingOrder(QueryBuilder $qb, $sortingColumn, $sortingOrder = 'DESC')
+    {
+        switch ($sortingColumn) {
+            case 'value':
+//                $sortingColumn = 'value';
+                $entity = 'h'; break;
+            case 'createdAt':
+            default:
+                $entity = 'h';
+        }
+
+        return $qb->orderBy($entity . '.' . $sortingColumn, $sortingOrder);
+    }
+
     public function add(Headword $entity, bool $flush = false): void
     {
         $this->getEntityManager()->persist($entity);
diff --git a/src/Twig/AppTwigExtension.php b/src/Twig/AppTwigExtension.php
index f10207a..e4a72f3 100644
--- a/src/Twig/AppTwigExtension.php
+++ b/src/Twig/AppTwigExtension.php
@@ -32,11 +32,11 @@ class AppTwigExtension extends AbstractExtension
          title='%s'>%s</div>", $entity->getRGB(), $entity, $entity->getInitials());
     }
 
-    public function badge($entity)
+    public function badge($entity, $title = null)
     {
         return sprintf("<div class='member-badge'
          style='background-color: rgb(%s); margin-top: -0.2rem;'
-         title='%s'>%s</div>", $entity->getRGB(), $entity, $entity->getInitials());
+         title='%s'>%s</div>", $entity->getRGB(), $title ? : $entity, $entity->getInitials());
     }
 
     public function labelClass(Label $label)
diff --git a/templates/label/_labelBadge.html.twig b/templates/label/_labelBadge.html.twig
index 1727d70..31a2744 100644
--- a/templates/label/_labelBadge.html.twig
+++ b/templates/label/_labelBadge.html.twig
@@ -1,4 +1,5 @@
-<div class="badge rounded-pill {{ label.isMilestone ? 'badge-milestone'}} position-relative text-black {{ label|labelClass }}" style="margin-right: 5px;">
+<div class="badge rounded-pill {{ label.isMilestone ? 'badge-milestone'}} position-relative text-black {{ label|labelClass }}" style="margin-right: 5px;"
+        title="{{ label.description }}">
     {{ label }}
     {% if label.isMilestone %}<br>{{ label.getMilestone|date('d/m/Y') }}{% endif %}
     {% if showHeadwordsCounter|default(false) %}
diff --git a/templates/label/_labelLine.html.twig b/templates/label/_labelLine.html.twig
index f785bab..8d9eb8a 100644
--- a/templates/label/_labelLine.html.twig
+++ b/templates/label/_labelLine.html.twig
@@ -1,6 +1,8 @@
 <tr>
     <td style="padding-bottom: 20px">
-        {% include "label/_labelBadge.html.twig" with {showHeadwordsCounter: true} %}
+        <a href="{{ path('app_label_show', {id: label.id}) }}">
+            {% include "label/_labelBadge.html.twig" with {showHeadwordsCounter: true} %}
+        </a>
     </td>
     {% for lexicon in app.user.myLexicons %}
         <td class="text-center col-collapse {{ (showVisibility|default(false) != category) ? 'hidden-column' }}">
diff --git a/templates/label/_labelsGeneral.html.twig b/templates/label/_labelsGeneral.html.twig
index bb6f886..a561a7b 100644
--- a/templates/label/_labelsGeneral.html.twig
+++ b/templates/label/_labelsGeneral.html.twig
@@ -19,7 +19,7 @@
             <tr>
                 <td class="d-flex justify-content-between">
                     <strong>{{ "Personnel"|trans }}</strong>
-                    <a title="{{ 'Ajouter un label'|trans }}" href="{{ path('app_label_new', {category: category, masterType: constant("App\\Entity\\Label::MASTER_PERSONAL")}) }}" class="btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
+                    <a title="{{ 'Ajouter un label'|trans }}" href="#" data-url="{{ path('app_label_new', {category: category, masterType: constant("App\\Entity\\Label::MASTER_PERSONAL")}) }}"  class="modal-form btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
                 </td>
                 <td colspan="{{ lexiconsNb }}"></td>
             </tr>
@@ -38,7 +38,7 @@
                 <tr>
                     <td>
                         {# {{ "Groupe"|trans }}:#} {{ group }}
-                        <a title="{{ 'Ajouter un label'|trans }}" href="{{ path('app_label_new', {groupId: group.id, category: category, masterType: constant("App\\Entity\\Label::MASTER_GROUP")}) }}" class="btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
+                        <a title="{{ 'Ajouter un label'|trans }}" href="#" data-url="{{ path('app_label_new', {groupId: group.id, category: category, masterType: constant("App\\Entity\\Label::MASTER_GROUP")}) }}"  class="modal-form btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
                     </td>
                     <td colspan="2"></td>
                 </tr>
@@ -51,7 +51,7 @@
             <tr>
                 <td class="d-flex justify-content-between">
                     <strong>{{ "Public"|trans }}</strong>
-                    <a title="{{ 'Ajouter un label'|trans }}" href="{{ path('app_label_new', {category: category, masterType: constant("App\\Entity\\Label::MASTER_PUBLIC")}) }}" class="btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
+                    <a title="{{ 'Ajouter un label'|trans }}" href="#" data-url="{{ path('app_label_new', {category: category, masterType: constant("App\\Entity\\Label::MASTER_PUBLIC")}) }}"  class="modal-form btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
                 </td>
                 <td colspan="{{ lexiconsNb }}"></td>
             </tr>
diff --git a/templates/label/_labelsInstitutional.html.twig b/templates/label/_labelsInstitutional.html.twig
index f14b9b1..b02c1f4 100644
--- a/templates/label/_labelsInstitutional.html.twig
+++ b/templates/label/_labelsInstitutional.html.twig
@@ -9,7 +9,7 @@
                 <td class="label-category">{{ "Institutionnel"|trans }}</td>
                 <td colspan="{{ lexiconsNb }}" class="text-end">
                     {% if is_granted('ROLE_TEACHER') %}
-                        <a title="{{ 'Ajouter un label'|trans }}" href="{{ path('app_label_new', {category: category, masterType: constant("App\\Entity\\Label::MASTER_PUBLIC")}) }}" class="btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
+                        <a title="{{ 'Ajouter un label'|trans }}" href="#" data-url="{{ path('app_label_new', {category: category, masterType: constant("App\\Entity\\Label::MASTER_PUBLIC")}) }}"  class="modal-form btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
                     {% endif %}
                     <button class="toggle-visibility btn btn-dark btn-sm" type="button">
                         <i class="fa fa-exchange"></i> {{ "Visibilité"|trans }}
diff --git a/templates/label/_labelsMilestone.html.twig b/templates/label/_labelsMilestone.html.twig
index 4739980..8f0269e 100644
--- a/templates/label/_labelsMilestone.html.twig
+++ b/templates/label/_labelsMilestone.html.twig
@@ -19,7 +19,7 @@
             <tr>
                 <td class="d-flex justify-content-between">
                     <strong>{{ "Personnel"|trans }}</strong>
-                    <a title="{{ 'Ajouter un label'|trans }}" href="{{ path('app_label_new', {category: category, masterType: constant("App\\Entity\\Label::MASTER_PERSONAL")}) }}" class="btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
+                    <a title="{{ 'Ajouter un label'|trans }}" href="#" data-url="{{ path('app_label_new', {category: category, masterType: constant("App\\Entity\\Label::MASTER_PERSONAL")}) }}"  class="modal-form btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
                 </td>
                 <td colspan="{{ lexiconsNb }}"></td>
             </tr>
@@ -38,7 +38,7 @@
                 <tr>
                     <td>
                         {# {{ "Groupe"|trans }}:#} {{ group }}
-                        <a title="{{ 'Ajouter un label'|trans }}" href="{{ path('app_label_new', {groupId: group.id, category: category, masterType: constant("App\\Entity\\Label::MASTER_GROUP")}) }}" class="btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
+                        <a title="{{ 'Ajouter un label'|trans }}" href="#" data-url="{{ path('app_label_new', {groupId: group.id, category: category, masterType: constant("App\\Entity\\Label::MASTER_GROUP")}) }}"  class="modal-form btn btn-dark btn-sm ms-2"><i class="bi-plus"></i></a>
                     </td>
                     <td colspan="2"></td>
                 </tr>
diff --git a/templates/label/_selectionActions.html.twig b/templates/label/_selectionActions.html.twig
new file mode 100644
index 0000000..eb8c7b2
--- /dev/null
+++ b/templates/label/_selectionActions.html.twig
@@ -0,0 +1,15 @@
+<div id="selectionActions" class="row">
+    <div class="col-md-12">
+        <i>{{ "Avec la sélection"|trans }}:</i>
+
+        <a title="{{ "Créer des entrées pour ces mots-vedettes dans un lexique"|trans }}" href="#" class="modal-form btn btn-light btn-sm"
+           data-extra-form-id="#headwordsSelection" data-url="{{ path('app_label_copy_headwords', {id: label.id}) }}">
+            <i class="fa fa-external-link"></i>
+        </a>
+        <button title="{{ "Supprimer le label pour ces mots-vedettes"|trans }}" type="submit" name="remove_label_from_headwords" class="btn btn-light btn-sm">
+            <i class="fa fa-trash"></i>
+        </button>
+
+    </div>
+</div>
+
diff --git a/templates/label/show.html.twig b/templates/label/show.html.twig
new file mode 100644
index 0000000..1edf582
--- /dev/null
+++ b/templates/label/show.html.twig
@@ -0,0 +1,183 @@
+{% extends 'base.html.twig' %}
+{#{% block container %}container-fluid{% endblock %}#}
+{% import 'macros.html.twig' as macros %}
+
+{% block title %}{{ "Label"|trans }} {{ label }}{% endblock %}
+
+{% block body %}
+
+        <div class="row my-5">
+            <div class="col-md-12">
+
+                <div class="row">
+                    <div class="col-md-6">
+
+                        <div class="d-flex justify-content-start align-items-center mt-3">
+                            <h1 class="d-flex justify-content-between">{% include "label/_labelBadge.html.twig" %}</h1>
+                            <a title="{{ 'Modifier'|trans }}" href="#" data-url="{{ path('app_label_edit', {id: label.id}) }}"  class="modal-form ms-3 btn btn-dark btn-sm"><i class="fa fa-pencil"></i></a>
+                            <a title="{{ 'Supprimer'|trans }}" href="#" data-href="{{ path('app_label_delete', {id: label.id}) }}" class="ms-1 btn btn-danger btn-sm"
+                               data-confirm="{{ "Confirmer la suppression ?"|trans }}" data-bs-toggle="modal" data-bs-target="#confirm-dialog">
+                                <i class="fa fa-trash"></i>
+                            </a>
+                        </div>
+
+                        <div class="d-flex justify-content-start align-items-center mt-3">
+                            <h4>{{ label.description|nl2br|raw }}</h4>
+                            <a title="{{ 'Modifier la description'|trans }}" href="#" data-url="{{ path('app_label_edit_description', {id: label.id}) }}"  class="modal-form ms-3 btn btn-dark btn-sm"><i class="fa fa-pencil"></i></a>
+                        </div>
+
+                    </div>
+
+                    <div class="col-md-6"></div>
+
+                </div>
+
+                {#     SEARCHBAR       #}
+                <div class="row mt-4">
+                    <div class="col-sm-3">
+                        {{ form_start(form) }}
+                        <div class="d-flex justify-content-between">
+                            {{ form_widget(form.searchString) }}
+                            <button type="submit" title="{{ "Filtrer"|trans }}" class="btn btn-block btn-dark" style="margin-left: 10px">
+                                <i class="fa fa-search"></i>
+                            </button>
+                            <a href="{{ path('app_label_show', {id: label.id}) }}" title="{{ "Réinitialiser"|trans }}" class="btn btn-block btn-light" style="margin-left: 10px">
+                                <i class="fa fa-times"></i>
+                            </a>
+                        </div>
+                        {{ form_end(form) }}
+                    </div>
+                </div>
+
+                <div class="row mt-4">
+                    <div class="col-md-12">
+
+                        <form name="headwords_selection" id="headwordsSelection" action="{{ path('app_label_process_selected_headwords', {id: label.id}) }}" method="post" novalidate="novalidate" autocomplete="off">
+
+                            {% set lexiconsNb = app.user.myLexicons|length %}
+
+                            <table class="table table-bordered w-auto">
+                                <thead>
+                                <tr>
+                                    <th class="text-center">
+                                        <div class="d-flex justify-content-around align-items-center">
+                                            <span>{{ "Mots-vedettes"|trans }}</span>
+                                            <a title="{{ "Ajouter"|trans }}" href="#" actionUrl="{{ path('app_label_add_headword', {id: label.id}) }}" class="modal-form btn btn-dark btn-sm"><i class="fa fa-plus"></i></a>
+                                    </th>
+                                    <th colspan="{{ lexiconsNb }}" class="text-center">{{ "Présence des mots dans les lexiques"|trans }}</th>
+                                </tr>
+                                <tr>
+{#                                    <th class="text-nowrap">#}
+{#                                        <span>{{ macros.sorting_column_with_filter("Ajout"|trans, 'createdAt', _context, {id: lexicon.id}) }}</span>#}
+{#                                    </th>#}
+                                    <th>
+                                        <div class="d-flex justify-content-between">
+                                            <i id="super-select" class="fa fa-square-o" data-bs-toggle="modal" data-bs-target="#selectModal"></i>
+                                            {{ macros.sorting_column_with_filter('<i class="fa fa-sort-alpha-asc"></i>', 'value', _context, {id: label.id}) }}
+                                            <a href="{{ path('app_label_show', {id: label.id}|merge({shuffle: 'SHUFFLE'})|merge(app.request.query.all|filter((value, key) => (key != 'sortingColumn' and key != 'sortingOrder')))) }}">
+                                                <i class="ms-3 fa fa-random"></i>
+                                            </a>
+                                        </div>
+                                    </th>
+                                    {% for lexicon in app.user.myLexicons %}
+                                        {% set labelVisibleInLexicon = label_manager.isVisible(label, lexicon, app.user) %}
+                                        <th class="text-center {{ labelVisibleInLexicon ? 'bg-green' : 'faded bg-grey' }}">
+                                            <a href="{{ path('app_label_toggle_visibility', {id: label.id, lexiconId: lexicon.id, backUrl: app.request.uri}) }}" class="overlay-on-click">
+                                                {{ lexicon|badge((labelVisibleInLexicon ? 'Masquer le label dans ce lexique' : 'Montrer le label dans ce lexique')|trans) }}
+                                            </a>
+                                        </th>
+                                    {% endfor %}
+                                </tr>
+                                </thead>
+                                <tbody>
+                                {% for headword in headwords %}
+
+                                    {# @var headword \App\Entity\Headword #}
+                                    <tr>
+{#                                        <td>{{entry.addingOrder }}</td>#}
+                                        <td style="min-width: 50%">
+                                                {# "value" est utilisé par Symfo quand on soumet le form. "data-headword-id" est utilisé par la vue chooseLabel pour injecter du json dans le sliens ajax #}
+                                            <input class="me-2" type="checkbox" name="form[selected_headwords][]"
+                                                   value="{{ headword.id }}" id="form_selected_headwords_{{ headword.id }}"/>
+                                            <a href="{{ path('app_wiktionnary_show', {id: headword.id}) }}">
+                                                {{ headword.value }}
+                                            </a>
+                                        </td>
+
+                                        {% for lexicon in app.user.myLexicons %}
+                                            {% set labelVisibleInLexicon = label_manager.isVisible(label, lexicon, app.user) %}
+                                            <td class="text-center {{ labelVisibleInLexicon ? '' : 'faded' }}">
+                                                {% if lexicon.getEntryForHeadword(headword) %}
+                                                    <i class="fa fa-check-square-o text-success"></i>
+                                                {% else %}
+                                                    <i class="fa fa-square-o text-black"></i>
+                                                {% endif %}
+                                            </td>
+                                        {% endfor %}
+
+                                    </tr>
+
+
+                                {% endfor %}
+                                </tbody>
+                            </table>
+
+                            {% include "label/_selectionActions.html.twig" %}
+
+                        </form>
+
+                    </div>
+                </div>
+
+            </div>
+        </div>
+
+{% endblock %}
+
+{% block javascripts %}
+    {{ parent() }}
+
+    <script type="text/javascript">
+        $(function() {
+
+            toggleSelectionActions();
+            $('[name="form[selected_headwords][]"]').on('change', function () {
+                toggleSelectionActions();
+            })
+
+            $('#selectNone').on('click', function() {
+                $('input:checkbox').prop('checked', false);
+                $('#selectModal').modal('hide');
+                toggleSelectionActions();
+            });
+            $('#selectAll').on('click', function() {
+                $('input:checkbox:visible').prop('checked', true);
+                $('#selectModal').modal('hide');
+                toggleSelectionActions();
+            });
+            $('#selectN').on('click', function(){
+                var nb = $(this).parent().find('input').val();
+                $('input:checkbox:visible').slice(0, nb).prop('checked', true);
+                $('#selectModal').modal('hide');
+                toggleSelectionActions();
+            });
+
+        });
+
+        function toggleSelectionActions() {
+            var $selected = $('[name="form[selected_headwords][]"]:checked');
+            if ($selected.length) {
+                $('#selectionActions').show();
+            } else {
+                $('#selectionActions').hide();
+            }
+
+            // création du json qui sera récupéré par les liens ajax-link à partir des entrées sélectionnées, si pas de tag data-json sur le lien ajax-link
+            // PAS UTILISÉ POUR L'INSTANT
+            var selectedValues = $selected.map(function (i, element) {
+                return $(element).val();
+            }).get();
+            $('#headwordsIds').val(JSON.stringify({headwords_ids: selectedValues}));
+        }
+    </script>
+{% endblock %}
\ No newline at end of file
diff --git a/templates/lexicon/show.html.twig b/templates/lexicon/show.html.twig
index abbc335..9e1f5c2 100644
--- a/templates/lexicon/show.html.twig
+++ b/templates/lexicon/show.html.twig
@@ -44,7 +44,12 @@
                                 {% endif %}
                             </tr>
                             <tr>
-                                <th>{{ macros.sorting_column_with_filter("Ajout"|trans, 'createdAt', _context, {id: lexicon.id}) }}</th>
+                                <th class="text-nowrap">
+                                    <span>{{ macros.sorting_column_with_filter("Ajout"|trans, 'createdAt', _context, {id: lexicon.id}) }}</span>
+                                    <a href="{{ path('app_lexicon_show', {id: lexicon.id}|merge({shuffle: 'SHUFFLE'})|merge(app.request.query.all|filter((value, key) => (key != 'sortingColumn' and key != 'sortingOrder')))) }}">
+                                        <i class="ms-3 fa fa-random"></i>
+                                    </a>
+                                </th>
                                 <th class="d-flex justify-content-between">
                                     <i id="super-select" class="fa fa-square-o" data-bs-toggle="modal" data-bs-target="#selectModal"></i>
                                     {{ macros.sorting_column_with_filter('<i class="fa fa-sort-alpha-asc"></i>', 'value', _context, {id: lexicon.id}) }}
@@ -64,14 +69,16 @@
                                     {# @var entry \App\Entity\Entry #}
                                     {% set known = entry.headword.knownByUser(app.user) %}
                                     <tr class="{{ known ? 'headword-known' }}">
-                                        <td>{{entry.addingOrder }}</td>
+                                        <td class="col-md-1">{{entry.addingOrder }}</td>
                                         <td class="d-flex justify-content-between">
-                                            <a href="{{ path('app_entry_show', {id: entry.id}) }}">
-                                                {# "value" est utilisé par Symfo quand on soumet le form. "data-headword-id" est utilisé par la vue chooseLabel pour injecter du json dans le sliens ajax #}
+                                            {# "value" est utilisé par Symfo quand on soumet le form. "data-headword-id" est utilisé par la vue chooseLabel pour injecter du json dans le sliens ajax #}
+                                            <div>
                                                 <input class="me-2" type="checkbox" name="form[selected_entries][]" data-headword-id="{{ entry.headword.id }}"
                                                        value="{{ entry.id }}" id="form_selected_entries_{{ entry.id }}"/>
-                                                {{ entry }}
-                                            </a>
+                                                <a href="{{ path('app_entry_show', {id: entry.id}) }}">
+                                                    {{ entry }}
+                                                </a>
+                                            </div>
                                             <a title="{{ known ? 'Mot-vedette connu. Cliquer pour modifier'|trans : 'Mot-vedette non connu. Cliquer pour modifier'|trans }}"
                                                href="#" class="ajax-link" data-method="GET" data-url="{{ path('app_headword_toggle_known', {id: entry.headword.id, userId: app.user.id, lexiconId: lexicon.id}) }}">
                                                 {% if known %}<i class="fa fa-circle text-success"></i>{% else %}<i class="fa fa-circle text-warning"></i>{% endif %}
-- 
GitLab