From b7b07e5abfeadf6cf58eed2c2c9cbd166ebdb957 Mon Sep 17 00:00:00 2001 From: afornerot Date: Wed, 17 Sep 2025 09:00:54 +0200 Subject: [PATCH] svg --- src/Controller/GroupController.php | 180 +++++++++++++ src/Controller/HomeController.php | 14 +- src/Controller/ProjectController.php | 15 ++ src/Entity/Group.php | 150 +++++++++++ src/Entity/GroupTimeline.php | 86 ++++++ src/Entity/User.php | 46 ++-- src/EventListener/GroupListener.php | 155 +++++++++++ src/Form/GroupType.php | 67 +++++ src/Repository/GroupRepository.php | 18 ++ src/Repository/GroupTimelineRepository.php | 18 ++ src/Security/FileVoter.php | 16 +- src/Security/GroupVoter.php | 95 +++++++ templates/group/edit.html.twig | 68 +++++ templates/group/list.html.twig | 44 +++ templates/group/timeline.html.twig | 57 ++++ templates/group/view.html.twig | 73 +++++ templates/home/home.html.twig | 295 ++++++++++++++------- 17 files changed, 1271 insertions(+), 126 deletions(-) create mode 100644 src/Controller/GroupController.php create mode 100644 src/Entity/Group.php create mode 100644 src/Entity/GroupTimeline.php create mode 100644 src/EventListener/GroupListener.php create mode 100644 src/Form/GroupType.php create mode 100644 src/Repository/GroupRepository.php create mode 100644 src/Repository/GroupTimelineRepository.php create mode 100644 src/Security/GroupVoter.php create mode 100644 templates/group/edit.html.twig create mode 100644 templates/group/list.html.twig create mode 100644 templates/group/timeline.html.twig create mode 100644 templates/group/view.html.twig diff --git a/src/Controller/GroupController.php b/src/Controller/GroupController.php new file mode 100644 index 0000000..8dd02bc --- /dev/null +++ b/src/Controller/GroupController.php @@ -0,0 +1,180 @@ +fileService = $fileService; + } + + #[Route('/admin/group', name: 'app_admin_group')] + public function list(GroupRepository $groupRepository): Response + { + $groups = $groupRepository->findAll(); + + return $this->render('group/list.html.twig', [ + 'usemenu' => true, + 'usesidebar' => true, + 'title' => 'Liste des Groupes', + 'routesubmit' => 'app_admin_group_submit', + 'routeupdate' => 'app_admin_group_update', + 'groups' => $groups, + ]); + } + + #[Route('/admin/group/submit', name: 'app_admin_group_submit')] + #[Route('/user/group/submit', name: 'app_user_group_submit')] + public function submit(Request $request, EntityManagerInterface $em): Response + { + $group = new Group(); + $group->addUser($this->getUser()); + $group->setStatus(Group::ACTIVE); + + $this->denyAccessUnlessGranted(GroupVoter::SUBMIT, $group); + + $isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin'); + $form = $this->createForm(GroupType::class, $group, ['mode' => 'submit']); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $em->persist($group); + $em->flush(); + + $this->fileService->init('group', $group->getId()); + + return $this->redirectToRoute($isAdmin ? 'app_admin_group_update' : 'app_user_group_update', ['id' => $group->getId()]); + } + + return $this->render('group/edit.html.twig', [ + 'usemenu' => true, + 'usesidebar' => $isAdmin, + 'title' => 'Création Groupe', + 'routecancel' => $isAdmin ? 'app_admin_group' : 'app_home', + 'routedelete' => $isAdmin ? 'app_admin_group_delete' : 'app_user_group_delete', + 'mode' => 'submit', + 'form' => $form, + ]); + } + + #[Route('/admin/group/update/{id}', name: 'app_admin_group_update')] + #[Route('/user/group/update/{id}', name: 'app_user_group_update')] + public function update(int $id, Request $request, GroupRepository $groupRepository, EntityManagerInterface $em): Response + { + $group = $groupRepository->find($id); + if (!$group) { + throw new NotFoundHttpException('La ressource demandée est introuvable.'); + } + + $this->denyAccessUnlessGranted(GroupVoter::UPDATE, $group); + + $isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin'); + $form = $this->createForm(GroupType::class, $group, ['mode' => 'update']); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $em->flush(); + + if ($isAdmin) { + return $this->redirectToRoute('app_admin_group'); + } else { + return $this->redirectToRoute('app_user_group_view', ['id' => $group->getId()]); + } + } + + return $this->render('group/edit.html.twig', [ + 'usemenu' => true, + 'usesidebar' => $isAdmin, + 'title' => 'Modification Groupe = '.$group->getTitle(), + 'routecancel' => $isAdmin ? 'app_admin_group' : 'app_home', + 'routemove' => $isAdmin ? 'app_admin_group_move' : 'app_user_group_move', + 'routedelete' => $isAdmin ? 'app_admin_group_delete' : 'app_user_group_delete', + 'routesubmitoption' => $isAdmin ? 'app_admin_option_submit' : 'app_user_option_submit', + 'routeupdateoption' => $isAdmin ? 'app_admin_option_update' : 'app_user_option_update', + 'mode' => 'update', + 'form' => $form, + 'group' => $group, + ]); + } + + #[Route('/admin/group/moveto/{id}/{status}', name: 'app_admin_group_move')] + #[Route('/user/group/moveto/{id}/{status}', name: 'app_user_group_move')] + public function move(int $id, string $status, Request $request, GroupRepository $groupRepository, EntityManagerInterface $em): Response + { + $group = $groupRepository->find($id); + if (!$group) { + throw new NotFoundHttpException('La ressource demandée est introuvable.'); + } + + $attribute = constant(GroupVoter::class.'::MOVE'.$status); + $this->denyAccessUnlessGranted($attribute, $group); + + $group->setStatus(constant(Group::class.'::'.$status)); + $em->flush(); + + $isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin'); + + return $this->redirectToRoute($isAdmin ? 'app_admin_group' : 'app_user_group_view', ['id' => $group->getId()]); + } + + #[Route('/admin/group/delete/{id}', name: 'app_admin_group_delete')] + #[Route('/user/group/delete/{id}', name: 'app_user_group_delete')] + public function delete(int $id, Request $request, GroupRepository $groupRepository, EntityManagerInterface $em): Response + { + $group = $groupRepository->find($id); + if (!$group) { + throw new NotFoundHttpException('La ressource demandée est introuvable.'); + } + + $this->denyAccessUnlessGranted(GroupVoter::DELETE, $group); + + $isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin'); + + // Tentative de suppression + try { + $em->remove($group); + $em->flush(); + } catch (\Exception $e) { + $this->addflash('error', $e->getMessage()); + } + + return $this->redirectToRoute($isAdmin ? 'app_admin_group' : 'app_home'); + } + + #[Route('/user/group/view/{id}', name: 'app_user_group_view')] + public function view(int $id, Request $request, GroupRepository $groupRepository, EntityManagerInterface $em): Response + { + $group = $groupRepository->find($id); + if (!$group) { + throw new NotFoundHttpException('La ressource demandée est introuvable.'); + } + + $this->denyAccessUnlessGranted(GroupVoter::VIEW, $group); + + $isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin'); + + return $this->render('group/view.html.twig', [ + 'usemenu' => true, + 'usesidebar' => false, + 'title' => 'Group = '.$group->getTitle(), + 'routeupdate' => $isAdmin ? 'app_admin_group_update' : 'app_user_group_update', + 'routecancel' => $isAdmin ? 'app_admin_group' : 'app_home', + 'routedelete' => $isAdmin ? 'app_admin_group_delete' : 'app_user_group_delete', + 'routemove' => $isAdmin ? 'app_admin_group_move' : 'app_user_group_move', + 'group' => $group, + ]); + } +} diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php index b17038d..1abc3fa 100644 --- a/src/Controller/HomeController.php +++ b/src/Controller/HomeController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use App\Repository\GroupRepository; use App\Repository\ProjectRepository; use App\Service\EtherpadService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -11,14 +12,16 @@ use Symfony\Component\Routing\Attribute\Route; class HomeController extends AbstractController { #[Route('/', name: 'app_home')] - public function home(ProjectRepository $projectRepository): Response + public function home(ProjectRepository $projectRepository, GroupRepository $groupRepository): Response { $projects = $projectRepository->findAll(); + $groups = $groupRepository->findAll(); return $this->render('home/home.html.twig', [ 'usemenu' => true, 'usesidebar' => false, 'projects' => $projects, + 'groups' => $groups, ]); } @@ -35,14 +38,7 @@ class HomeController extends AbstractController public function etherpad(string $id, EtherpadService $etherpadService): Response { $padAccess = $etherpadService->preparePadAccess($this->getUser(), $id); - dump(vars: $padAccess); - $response = $this->render('etherpad/show.html.twig', [ - 'iframeUrl' => $padAccess['iframeUrl'], - ]); - - $response->headers->setCookie($padAccess['cookie']); - - return $response; + return $this->redirect($padAccess['iframeUrl']); } } diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 759f4ab..95a255f 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -3,6 +3,7 @@ namespace App\Controller; use App\Entity\Project; +use App\Entity\ProjectOption; use App\Form\ProjectType; use App\Form\ProjectVotedType; use App\Repository\ProjectRepository; @@ -55,6 +56,20 @@ class ProjectController extends AbstractController if ($form->isSubmitted() && $form->isValid()) { $em->persist($project); $em->flush(); + + // Création d'option par défaut + $option = new ProjectOption(); + $option->setProject($project); + $option->setTitle('Pour'); + $em->persist($option); + $em->flush(); + + $option = new ProjectOption(); + $option->setProject($project); + $option->setTitle('Contre'); + $em->persist($option); + $em->flush(); + $this->fileService->init('project', $project->getId()); return $this->redirectToRoute($isAdmin ? 'app_admin_project_update' : 'app_user_project_update', ['id' => $project->getId()]); diff --git a/src/Entity/Group.php b/src/Entity/Group.php new file mode 100644 index 0000000..41d5e2b --- /dev/null +++ b/src/Entity/Group.php @@ -0,0 +1,150 @@ + + */ + #[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'groups')] + private Collection $users; + + /** + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'group', targetEntity: GroupTimeline::class, cascade: ['remove'], orphanRemoval: true)] + #[ORM\OrderBy(['createdAt' => 'DESC'])] + private Collection $timelines; + + public function __construct() + { + $this->users = new ArrayCollection(); + $this->timelines = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): static + { + $this->title = $title; + + return $this; + } + + public function getSummary(): string + { + return $this->summary; + } + + public function setSummary(string $summary): static + { + $this->summary = $summary; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): static + { + $this->description = $description; + + return $this; + } + + public function getStatus(): string + { + return $this->status; + } + + public function setStatus(string $status): static + { + $this->status = $status; + + return $this; + } + + public function getDueDate(): ?\DateTimeInterface + { + return $this->dueDate; + } + + public function setDueDate(?\DateTimeInterface $dueDate): static + { + $this->dueDate = $dueDate; + + return $this; + } + + public function getUsers(): Collection + { + return $this->users; + } + + public function addUser(User $user): static + { + if (!$this->users->contains($user)) { + $this->users->add($user); + $user->addGroup($this); + } + + return $this; + } + + public function removeUser(User $user): static + { + if ($this->users->removeElement($user)) { + $user->removeGroup($this); + } + + return $this; + } + + public function getTimelines(): Collection + { + return $this->timelines; + } +} diff --git a/src/Entity/GroupTimeline.php b/src/Entity/GroupTimeline.php new file mode 100644 index 0000000..714a955 --- /dev/null +++ b/src/Entity/GroupTimeline.php @@ -0,0 +1,86 @@ +id; + } + + public function getGroup(): Group + { + return $this->group; + } + + public function setGroup(Group $group): static + { + $this->group = $group; + + return $this; + } + + public function getUser(): User + { + return $this->user; + } + + public function setUser(User $user): static + { + $this->user = $user; + + return $this; + } + + public function getCreatedAt(): \DateTimeInterface + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeInterface $createAt): static + { + $this->createdAt = $createAt; + + return $this; + } + + public function getDescription(): array + { + return $this->description; + } + + public function setDescription(array $description): static + { + $this->description = $description; + + return $this; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 1cbee03..22091fd 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -45,13 +45,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\ManyToMany(targetEntity: Project::class, inversedBy: 'users')] private ?Collection $projects; - #[ORM\ManyToOne()] - #[ORM\JoinColumn(nullable: true)] - private ?Project $project = null; + #[ORM\ManyToMany(targetEntity: Group::class, inversedBy: 'users')] + private ?Collection $groups; public function __construct() { $this->projects = new ArrayCollection(); + $this->groups = new ArrayCollection(); } public function getId(): ?int @@ -71,21 +71,11 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } - /** - * A visual identifier that represents this user. - * - * @see UserInterface - */ public function getUserIdentifier(): string { return (string) $this->username; } - /** - * @see UserInterface - * - * @return list - */ public function getRoles(): array { $roles = $this->roles; @@ -95,9 +85,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return array_unique($roles); } - /** - * @param list $roles - */ public function setRoles(array $roles): static { $this->roles = $roles; @@ -125,9 +112,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } - /** - * @see UserInterface - */ public function eraseCredentials(): void { // If you store any temporary, sensitive data on the user, clear it here @@ -194,18 +178,26 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } - public function getProject(): ?Project + /** + * @return Collection + */ + public function getGroups(): ?Collection { - if (!$this->projects) { - return null; - } - - return $this->project; + return $this->groups; } - public function setProject(?Project $project): static + public function addGroup(Group $group): static { - $this->project = $project; + if (!$this->groups->contains($group)) { + $this->groups->add($group); + } + + return $this; + } + + public function removeGroup(Group $group): static + { + $this->groups->removeElement($group); return $this; } diff --git a/src/EventListener/GroupListener.php b/src/EventListener/GroupListener.php new file mode 100644 index 0000000..3f4dfb5 --- /dev/null +++ b/src/EventListener/GroupListener.php @@ -0,0 +1,155 @@ +getObjectManager(); + if (!$em instanceof EntityManagerInterface) { + return; + } + $uow = $em->getUnitOfWork(); + + $user = $this->security->getUser(); + if (!$user) { + return; + } + + foreach ($uow->getScheduledEntityInsertions() as $entity) { + if (!$entity instanceof Group) { + continue; + } + + $timeline = new GroupTimeline(); + $timeline->setGroup($entity); + $timeline->setUser($user); + $timeline->setCreatedAt(new \DateTime()); + $timeline->setDescription(['created' => true]); + + $em->persist($timeline); + $meta = $em->getClassMetadata(GroupTimeline::class); + $uow->computeChangeSet($meta, $timeline); + } + + foreach ($uow->getScheduledEntityUpdates() as $entity) { + if (!$entity instanceof Group) { + continue; + } + + $changeSet = $uow->getEntityChangeSet($entity); + $changes = []; + + foreach ($changeSet as $field => [$old, $new]) { + $oldStr = $this->stringify($old); + $newStr = $this->stringify($new); + + if ($oldStr !== $newStr) { + $changes[$field] = [ + 'old' => $oldStr, + 'new' => $newStr, + ]; + } + } + + if (!empty($changes)) { + $timeline = new GroupTimeline(); + $timeline->setGroup($entity); + $timeline->setUser($user); + $timeline->setCreatedAt(new \DateTime()); + $timeline->setDescription($changes); + + $em->persist($timeline); + $meta = $em->getClassMetadata(GroupTimeline::class); + $uow->computeChangeSet($meta, $timeline); + } + } + + foreach ($uow->getScheduledCollectionUpdates() as $col) { + $owner = $col->getOwner(); + + if (!$owner instanceof Group) { + continue; + } + + $mapping = $col->getMapping(); + $fieldName = $mapping['fieldName']; + + $added = $col->getInsertDiff(); + $removed = $col->getDeleteDiff(); + + $changes = []; + + foreach ($added as $addedEntity) { + $changes[$fieldName.'_added'][] = $this->stringify($addedEntity); + } + + foreach ($removed as $removedEntity) { + $changes[$fieldName.'_removed'][] = $this->stringify($removedEntity); + } + + if (!empty($changes)) { + $timeline = new GroupTimeline(); + $timeline->setGroup($owner); + $timeline->setUser($user); + $timeline->setCreatedAt(new \DateTime()); + $timeline->setDescription($changes); + + $em->persist($timeline); + $meta = $em->getClassMetadata(GroupTimeline::class); + $uow->computeChangeSet($meta, $timeline); + } + } + } + + private function stringify(mixed $value): string + { + if (is_null($value)) { + return 'null'; + } + + if (is_scalar($value)) { + return (string) $value; + } + + if ($value instanceof Collection || is_array($value)) { + $elements = []; + foreach ($value as $item) { + $elements[] = $this->stringify($item); + } + + return '['.implode(', ', $elements).']'; + } + + if (is_object($value)) { + if (method_exists($value, '__toString')) { + return (string) $value; + } + + foreach (['getUsername', 'getName', 'getTitle', 'getEmail', 'getId'] as $method) { + if (method_exists($value, $method)) { + return (string) $value->{$method}(); + } + } + + return get_class($value); + } + + return json_encode($value); + } +} diff --git a/src/Form/GroupType.php b/src/Form/GroupType.php new file mode 100644 index 0000000..0ee535c --- /dev/null +++ b/src/Form/GroupType.php @@ -0,0 +1,67 @@ +add('submit', SubmitType::class, [ + 'label' => 'Valider', + 'attr' => ['class' => 'btn btn-success no-print me-1'], + ]) + + ->add('title', TextType::class, [ + 'label' => 'Titre', + ]) + + ->add('dueDate', DateType::class, [ + 'label' => 'A Voter pour le', + 'required' => false, + 'html5' => true, + ]) + + ->add('summary', TextareaType::class, [ + 'label' => 'Résumé', + ]) + + ->add('users', EntityType::class, [ + 'label' => 'Propriétaires', + 'class' => User::class, + 'choice_label' => 'username', + 'multiple' => true, + 'attr' => ['class' => 'select2'], + 'required' => false, + 'by_reference' => false, + ]); + + if ('update' == $options['mode']) { + $builder + ->add('description', MarkdownType::class, [ + 'label' => 'Description du Projet', + 'markdown_height' => 900, + ]); + } + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Group::class, + 'mode' => 'submit', + ]); + } +} diff --git a/src/Repository/GroupRepository.php b/src/Repository/GroupRepository.php new file mode 100644 index 0000000..283eebd --- /dev/null +++ b/src/Repository/GroupRepository.php @@ -0,0 +1,18 @@ + + */ +class GroupRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Group::class); + } +} diff --git a/src/Repository/GroupTimelineRepository.php b/src/Repository/GroupTimelineRepository.php new file mode 100644 index 0000000..ebefd66 --- /dev/null +++ b/src/Repository/GroupTimelineRepository.php @@ -0,0 +1,18 @@ + + */ +class GroupTimelineRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, GroupTimeline::class); + } +} diff --git a/src/Security/FileVoter.php b/src/Security/FileVoter.php index 7ac6820..3b25680 100644 --- a/src/Security/FileVoter.php +++ b/src/Security/FileVoter.php @@ -3,6 +3,7 @@ namespace App\Security; use App\Entity\User; +use App\Repository\GroupRepository; use App\Repository\ProjectRepository; use Bnine\FilesBundle\Security\AbstractFileVoter; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -10,8 +11,9 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; class FileVoter extends AbstractFileVoter { private ProjectRepository $projectRepository; + private GroupRepository $groupRepository; - public function __construct(ProjectRepository $projectRepository) + public function __construct(ProjectRepository $projectRepository, GroupRepository $groupRepository) { $this->projectRepository = $projectRepository; } @@ -43,6 +45,12 @@ class FileVoter extends AbstractFileVoter return true; } break; + case 'group': + $group = $this->groupRepository->find($id); + if ($group && $group->getUsers()->contains($user)) { + return true; + } + break; } return false; @@ -65,6 +73,12 @@ class FileVoter extends AbstractFileVoter return true; } break; + case 'group': + $group = $this->groupRepository->find($id); + if ($group && $group->getUsers()->contains($user)) { + return true; + } + break; } return false; diff --git a/src/Security/GroupVoter.php b/src/Security/GroupVoter.php new file mode 100644 index 0000000..b17b373 --- /dev/null +++ b/src/Security/GroupVoter.php @@ -0,0 +1,95 @@ +hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER'); + $hasUser = $group->getUsers()->contains($user); + $hasStatus = Group::ACTIVE === $group->getStatus(); + + return $hasMaster || ($hasUser && $hasStatus); + } + + private function canDelete(Group $group, User $user): bool + { + $hasMaster = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER'); + $hasUser = $group->getUsers()->contains($user); + $hasStatus = Group::ACTIVE === $group->getStatus(); + + return $hasMaster || ($hasUser && $hasStatus); + } + + private function canMoveToInactive(Group $group, User $user): bool + { + $hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER') || $group->getUsers()->contains($user); + $hasStatus = Group::ACTIVE === $group->getStatus(); + + return $hasUser && $hasStatus; + } + + private function canMoveToActive(Group $group, User $user): bool + { + $hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER') || $group->getUsers()->contains($user); + $hasStatus = Group::INACTIVE === $group->getStatus(); + + return $hasUser && $hasStatus; + } + + private function toMe(Group $group, User $user): bool + { + return $group->getUsers()->contains($user); + } + + protected function voteOnAttribute(string $attribute, $group, TokenInterface $token): bool + { + $user = $token->getUser(); + if (!$user instanceof User) { + return false; + } + + return match ($attribute) { + self::VIEW => $this->canView($group, $user), + self::SUBMIT => $this->canSubmit($group, $user), + self::UPDATE => $this->canUpdate($group, $user), + self::DELETE => $this->canDelete($group, $user), + self::MOVETOACTIVE => $this->canMoveToActive($group, $user), + self::MOVETOINACTIVE => $this->canMoveToInactive($group, $user), + self::TOME => $this->toMe($group, $user), + default => false, + }; + } +} diff --git a/templates/group/edit.html.twig b/templates/group/edit.html.twig new file mode 100644 index 0000000..99aa6d5 --- /dev/null +++ b/templates/group/edit.html.twig @@ -0,0 +1,68 @@ +{% extends 'base.html.twig' %} + +{% block title %} = {{title}}{% endblock %} + +{% block body %} +

{{title}}

+ + {{ form_start(form) }} + {{ form_widget(form.submit) }} + Annuler + {% if mode=="update" and is_granted('DELETE', group) %} + Supprimer + {% endif %} + + {% include('include/error.html.twig') %} + +
+
+
+
Information
+
+ {{ form_row(form.title) }} + {{ form_row(form.dueDate) }} + {{ form_row(form.users) }} + {{ form_row(form.summary) }} +
+
+ + {% if mode=="update" %} + {{ render(path("bninefiles_files",{domain:'group',id:group.id, editable:1})) }} + +
+
Timeline
+
+ {% include('group/timeline.html.twig') %} +
+
+ {% endif %} +
+ + {% if mode=="update" %} +
+
+
Pad du Groupe
+
+ {{ form_widget(form.description) }} +
+
+
+ {% endif %} +
+ {{ form_end(form) }} +{% endblock %} + +{% block localscript %} + +{% endblock %} diff --git a/templates/group/list.html.twig b/templates/group/list.html.twig new file mode 100644 index 0000000..6442c21 --- /dev/null +++ b/templates/group/list.html.twig @@ -0,0 +1,44 @@ +{% extends 'base.html.twig' %} + +{% block title %} = {{title}}{% endblock %} + +{% block body %} +

{{title}}

+ Ajouter + +
+ + + + + + + + + + {% for project in projects %} + + + + + + {% endfor %} + +
ActionTitleModifié le
+ + {{project.title}}{{project.timelines.first ? project.timelines.first.createdAt|date('d/m/Y H:i') : '' }}
+
+{% endblock %} + +{% block localscript %} + +{% endblock %} diff --git a/templates/group/timeline.html.twig b/templates/group/timeline.html.twig new file mode 100644 index 0000000..3dda462 --- /dev/null +++ b/templates/group/timeline.html.twig @@ -0,0 +1,57 @@ + + +
+ {% for event in group.timelines %} +
+
+
+

+ {{ event.createdAt|date('d/m/Y H:i') }} par {{ event.user.username }}
+ {% for field, change in event.description %} + {{ field }} + {% if field=="status" %} + {{ change.old ?? '' }} + → + {{ change.new ?? '' }} + {% endif %} + + {% if not change.old is defined and change[0] is defined %} + {{ change[0] }} + {% endif %} +
+ {% endfor %} +

+
+
+ {% endfor %} +
+ + diff --git a/templates/group/view.html.twig b/templates/group/view.html.twig new file mode 100644 index 0000000..25f8ecb --- /dev/null +++ b/templates/group/view.html.twig @@ -0,0 +1,73 @@ +{% extends 'base.html.twig' %} + +{% block title %} = {{title}}{% endblock %} + +{% block body %} +

+
{{title}}
+
{{group.status}}
+

+ +
+ {% if is_granted('UPDATE', group) %} + Modifier + {% endif %} + + Retour + + {% if is_granted('MOVETOINACTIF', group) and group.status=="Actif"%} + + Statut = Inactif + + {% endif %} + + {% if is_granted('DELETE', group) %} + + Supprimer + + {% endif %} + {% if is_granted('MOVETOACTIF', group) and group.status=="Inactif" %} + + Statut = Actif + + {% endif %} +
+ +
+
+
+
Information
+
+ Titre = {{ group.title }}
+ A Voter pour le = {{ group.dueDate ? group.dueDate|date("d/m/Y"):"" }} +
+
+ +
+
Résumé
+
+ {{ group.summary|nl2br }} +
+
+ + {{ render(path("bninefiles_files",{domain:'group',id:group.id, editable:0})) }} + +
+
Timeline
+
+ {% include('group/timeline.html.twig') %} +
+
+
+ +
+
+
Pad du Group
+
+ {{ group.description ? group.description|markdown_to_html : "" }} +
+
+
+
+{% endblock %} + diff --git a/templates/home/home.html.twig b/templates/home/home.html.twig index 148f839..344b278 100644 --- a/templates/home/home.html.twig +++ b/templates/home/home.html.twig @@ -3,115 +3,174 @@ {%block body%} -

Projets

- +
+
+

Projets

+ -
-

Brouillon

-
-
-

A Voter

-
-
-

Voté

-
-
-

Archivé

-
+
+

Brouillon

+
+
+

A Voter

+
+
+

Voté

+
+
+

Archivé

+
-
- {% for project in projects %} - {% if is_granted('VIEW', project) %} -
-
-
{{project.title}}
+
+ {% for project in projects %} + {% if is_granted('VIEW', project) %} +
+
+
{{project.title}}
-
- -
-
-
- {{project.summary|nl2br}} - - {% if project.status=="Voté" or project.status=="Archivé" %} - -
-
-
- {% for option in project.options %} - {{ option.title|title }} = {{ option.nbVote }}
- {% endfor %} +
+ +
-
- Votes Blancs = {{ project.nbVoteWhite }}
- Votes Nuls = {{ project.nbVoteNull }} +
+ {{project.summary|nl2br}} + + {% if project.status=="Voté" or project.status=="Archivé" %} + +
+
+
+ {% for option in project.options %} + {{ option.title|title }} = {{ option.nbVote }}
+ {% endfor %} +
+
+ Votes Blancs = {{ project.nbVoteWhite }}
+ Votes Nuls = {{ project.nbVoteNull }} +
+
+ +
+ Résultat =
+ {{ project.resultVote|nl2br }} +
+ {% endif %} +
+ +
- -
- Résultat =
- {{ project.resultVote|nl2br }} -
- {% endif %} -
- - + {% endfor %}
- {% endif %} - {% endfor %} -
+
+ +
+

Groupes

+ + +
+

Actif

+
+
+

Inactif

+
+ +
+ {% for group in groups %} + {% if is_granted('VIEW', group) %} +
+
+
{{group.title}}
+ +
+ +
+
+
+ {{group.summary|nl2br}} +
+ + +
+ {% endif %} + {% endfor %} +
+
+
{%endblock%} {% block localscript %} {% endblock %}