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