This commit is contained in:
2025-08-05 22:37:43 +02:00
parent fc7494be40
commit efea97228b
15 changed files with 486 additions and 64 deletions

34
composer.lock generated
View File

@ -420,16 +420,16 @@
},
{
"name": "doctrine/dbal",
"version": "3.10.0",
"version": "3.10.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214"
"reference": "3626601014388095d3af9de7e9e958623b7ef005"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/1cf840d696373ea0d58ad0a8875c0fadcfc67214",
"reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/3626601014388095d3af9de7e9e958623b7ef005",
"reference": "3626601014388095d3af9de7e9e958623b7ef005",
"shasum": ""
},
"require": {
@ -514,7 +514,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.10.0"
"source": "https://github.com/doctrine/dbal/tree/3.10.1"
},
"funding": [
{
@ -530,7 +530,7 @@
"type": "tidelift"
}
],
"time": "2025-07-10T21:11:04+00:00"
"time": "2025-08-05T12:18:06+00:00"
},
{
"name": "doctrine/deprecations",
@ -1221,16 +1221,16 @@
},
{
"name": "doctrine/orm",
"version": "3.5.0",
"version": "3.5.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/orm.git",
"reference": "6deec3655ba3e8f15280aac11e264225854d2369"
"reference": "64444dcfd511089d526cd2c7f74b9d7ed583bdfc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/orm/zipball/6deec3655ba3e8f15280aac11e264225854d2369",
"reference": "6deec3655ba3e8f15280aac11e264225854d2369",
"url": "https://api.github.com/repos/doctrine/orm/zipball/64444dcfd511089d526cd2c7f74b9d7ed583bdfc",
"reference": "64444dcfd511089d526cd2c7f74b9d7ed583bdfc",
"shasum": ""
},
"require": {
@ -1305,9 +1305,9 @@
],
"support": {
"issues": "https://github.com/doctrine/orm/issues",
"source": "https://github.com/doctrine/orm/tree/3.5.0"
"source": "https://github.com/doctrine/orm/tree/3.5.1"
},
"time": "2025-07-01T17:40:53+00:00"
"time": "2025-08-05T06:05:51+00:00"
},
{
"name": "doctrine/persistence",
@ -10086,16 +10086,16 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.21",
"version": "2.1.22",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4",
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4",
"shasum": ""
},
"require": {
@ -10140,7 +10140,7 @@
"type": "github"
}
],
"time": "2025-07-28T19:35:08+00:00"
"time": "2025-08-04T19:17:37+00:00"
},
{
"name": "phpstan/phpstan-doctrine",

View File

@ -56,12 +56,12 @@ class ProjectController extends AbstractController
$em->flush();
$this->fileService->init('project', $project->getId());
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_user_update', ['id' => $project->getId()]);
return $this->redirectToRoute($isAdmin ? 'app_admin_project_update' : 'app_user_project_update', ['id' => $project->getId()]);
}
return $this->render('project/edit.html.twig', [
'usemenu' => true,
'usesidebar' => true,
'usesidebar' => $isAdmin,
'title' => 'Création Projet',
'routecancel' => $isAdmin ? 'app_admin_project' : 'app_home',
'routedelete' => $isAdmin ? 'app_admin_project_delete' : 'app_user_project_delete',
@ -87,7 +87,11 @@ class ProjectController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_home');
if ($isAdmin) {
return $this->redirectToRoute('app_admin_project');
} else {
return $this->redirectToRoute('app_user_project_view', ['id' => $project->getId()]);
}
}
return $this->render('project/edit.html.twig', [
@ -95,7 +99,10 @@ class ProjectController extends AbstractController
'usesidebar' => $isAdmin,
'title' => 'Modification Projet = '.$project->getTitle(),
'routecancel' => $isAdmin ? 'app_admin_project' : 'app_home',
'routemove' => $isAdmin ? 'app_admin_project_move' : 'app_user_project_move',
'routedelete' => $isAdmin ? 'app_admin_project_delete' : 'app_user_project_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,
'project' => $project,
@ -119,7 +126,7 @@ class ProjectController extends AbstractController
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_home');
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_user_project_view', ['id' => $project->getId()]);
}
#[Route('/admin/project/delete/{id}', name: 'app_admin_project_delete')]
@ -141,8 +148,6 @@ class ProjectController extends AbstractController
$em->flush();
} catch (\Exception $e) {
$this->addflash('error', $e->getMessage());
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_user_update', ['id' => $project->getId()]);
}
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_home');
@ -158,11 +163,16 @@ class ProjectController extends AbstractController
$this->denyAccessUnlessGranted(ProjectVoter::VIEW, $project);
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
return $this->render('project/view.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Projet = '.$project->getTitle(),
'routecancel' => 'app_home',
'routeupdate' => $isAdmin ? 'app_admin_project_update' : 'app_user_project_update',
'routecancel' => $isAdmin ? 'app_admin_project' : 'app_home',
'routedelete' => $isAdmin ? 'app_admin_project_delete' : 'app_user_project_delete',
'routemove' => $isAdmin ? 'app_admin_project_move' : 'app_user_project_move',
'project' => $project,
]);
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Controller;
use App\Entity\ProjectOption;
use App\Form\OptionType;
use App\Repository\ProjectOptionRepository;
use App\Repository\ProjectRepository;
use App\Security\ProjectVoter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
class ProjectOptionController extends AbstractController
{
#[Route('/admin/option/submit/{idproject}', name: 'app_admin_option_submit')]
#[Route('/user/option/submit/{idproject}', name: 'app_user_option_submit')]
public function submit(int $idproject, Request $request, ProjectRepository $projectRepository, EntityManagerInterface $em): Response
{
$project = $projectRepository->find($idproject);
if (!$project) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$this->denyAccessUnlessGranted(ProjectVoter::UPDATE, $project);
$option = new ProjectOption();
$option->setProject($project);
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
$form = $this->createForm(OptionType::class, $option, ['mode' => 'submit']);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($option);
$em->flush();
return $this->redirectToRoute($isAdmin ? 'app_admin_project_update' : 'app_user_project_update', ['id' => $idproject]);
}
return $this->render('option/edit.html.twig', [
'usemenu' => true,
'usesidebar' => $isAdmin,
'title' => 'Création Option',
'routecancel' => $isAdmin ? 'app_admin_project_update' : 'app_admin_project_update',
'routedelete' => $isAdmin ? 'app_admin_option_delete' : 'app_user_option_delete',
'mode' => 'submit',
'idproject' => $idproject,
'form' => $form,
]);
}
#[Route('/admin/option/update/{idproject}/{id}', name: 'app_admin_option_update')]
#[Route('/user/option/update/{idproject}/{id}', name: 'app_user_option_update')]
public function update(int $idproject, int $id, Request $request, ProjectOptionRepository $projectOptionResitory, EntityManagerInterface $em): Response
{
$option = $projectOptionResitory->find($id);
if (!$option) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$this->denyAccessUnlessGranted(ProjectVoter::UPDATE, $option->getProject());
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
$form = $this->createForm(OptionType::class, $option, ['mode' => 'update']);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
return $this->redirectToRoute($isAdmin ? 'app_admin_project_update' : 'app_user_project_update', ['id' => $idproject]);
}
return $this->render('option/edit.html.twig', [
'usemenu' => true,
'usesidebar' => $isAdmin,
'title' => 'Modification Option = '.$option->getTitle(),
'routecancel' => $isAdmin ? 'app_admin_project_update' : 'app_user_project_update',
'routedelete' => $isAdmin ? 'app_admin_option_delete' : 'app_user_option_delete',
'mode' => 'update',
'idproject' => $idproject,
'option' => $option,
'form' => $form,
]);
}
#[Route('/admin/option/delete/{idproject}/{id}', name: 'app_admin_option_delete')]
#[Route('/user/option/delete/{idproject}/{id}', name: 'app_user_option_delete')]
public function delete(int $idproject, int $id, Request $request, ProjectOptionRepository $projectOptionResitory, EntityManagerInterface $em): Response
{
$option = $projectOptionResitory->find($id);
if (!$option) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
$this->denyAccessUnlessGranted(ProjectVoter::UPDATE, $option->getProject());
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
// Tentative de suppression
try {
$em->remove($option);
$em->flush();
} catch (\Exception $e) {
$this->addflash('error', $e->getMessage());
}
return $this->redirectToRoute($isAdmin ? 'app_admin_project_update' : 'app_user_project_update', ['id' => $idproject]);
}
}

View File

@ -55,10 +55,18 @@ class Project
#[ORM\OrderBy(['createdAt' => 'DESC'])]
private Collection $timelines;
/**
* @var Collection<int, ProjectTimeline>
*/
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectOption::class, cascade: ['remove'])]
#[ORM\OrderBy(['title' => 'ASC'])]
private Collection $options;
public function __construct()
{
$this->users = new ArrayCollection();
$this->timelines = new ArrayCollection();
$this->options = new ArrayCollection();
}
public function getId(): ?int
@ -169,4 +177,9 @@ class Project
{
return $this->timelines;
}
public function getOptions(): Collection
{
return $this->options;
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Entity;
use App\Repository\ProjectOptionRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ProjectOptionRepository::class)]
class ProjectOption
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Project::class, inversedBy: 'timelines')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Project $project;
#[ORM\Column(length: 255)]
private string $title;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $whyYes = null;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $whyNot = null;
public function getId(): ?int
{
return $this->id;
}
public function getProject(): Project
{
return $this->project;
}
public function setProject(Project $project): self
{
$this->project = $project;
return $this;
}
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getWhyYes(): ?string
{
return $this->whyYes;
}
public function setWhyYes(?string $whyYes): self
{
$this->whyYes = $whyYes;
return $this;
}
public function getWhyNot(): ?string
{
return $this->whyNot;
}
public function setWhyNot(?string $whyNot): self
{
$this->whyNot = $whyNot;
return $this;
}
}

45
src/Form/OptionType.php Normal file
View File

@ -0,0 +1,45 @@
<?php
namespace App\Form;
use App\Entity\ProjectOption;
use Bnine\MdEditorBundle\Form\Type\MarkdownType;
use Symfony\Component\Form\AbstractType;
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;
class OptionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('submit', SubmitType::class, [
'label' => 'Valider',
'attr' => ['class' => 'btn btn-success no-print me-1'],
])
->add('title', TextType::class, [
'label' => 'Titre',
])
->add('whyYes', MarkdownType::class, [
'label' => 'Pourquoi Voter Pour',
'markdown_height' => 200,
])
->add('whyNot', MarkdownType::class, [
'label' => 'Pourquoi Voter Contre',
'markdown_height' => 200,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ProjectOption::class,
'mode' => 'submit',
]);
}
}

View File

@ -21,7 +21,7 @@ class ProjectType extends AbstractType
$builder
->add('submit', SubmitType::class, [
'label' => 'Valider',
'attr' => ['class' => 'btn btn-success no-print'],
'attr' => ['class' => 'btn btn-success no-print me-1'],
])
->add('title', TextType::class, [

View File

@ -24,7 +24,7 @@ class UserType extends AbstractType
$builder
->add('submit', SubmitType::class, [
'label' => 'Valider',
'attr' => ['class' => 'btn btn-success no-print'],
'attr' => ['class' => 'btn btn-success no-print me-1'],
])
->add('username', TextType::class, [

View File

@ -0,0 +1,18 @@
<?php
namespace App\Repository;
use App\Entity\ProjectOption;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<ProjectOption>
*/
class ProjectOptionRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ProjectOption::class);
}
}

View File

@ -16,10 +16,12 @@ class ProjectVoter extends Voter
public const DELETE = 'DELETE';
public const MOVEDRAFT = 'MOVEDRAFT';
public const MOVETOVOTE = 'MOVETOVOTE';
public const MOVEVOTED = 'MOVEVOTED';
public const MOVEARCHIVED = 'MOVEARCHIVED';
protected function supports(string $attribute, $subject): bool
{
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE];
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE, self::MOVEVOTED, self::MOVEARCHIVED];
return in_array($attribute, $attributes) && $subject instanceof Project;
}
@ -36,22 +38,58 @@ class ProjectVoter extends Voter
private function canUpdate(Project $project, User $user): bool
{
return $user->hasRole('ROLE_ADMIN') || (Project::DRAFT === $project->getStatus() && $project->getUsers()->contains($user));
$hasMaster = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasUser = $project->getUsers()->contains($user);
$hasStatus = Project::DRAFT === $project->getStatus();
return $hasMaster || ($hasUser && $hasStatus);
}
private function canDelete(Project $project, User $user): bool
{
return $user->hasRole('ROLE_ADMIN') || (Project::DRAFT === $project->getStatus() && $project->getUsers()->contains($user));
$hasMaster = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasUser = $project->getUsers()->contains($user);
$hasStatus = Project::DRAFT === $project->getStatus();
return $hasMaster || ($hasUser && $hasStatus);
}
private function canMoveDraft(Project $project, User $user): bool
{
return $user->hasRole('ROLE_ADMIN') || (Project::TOVOTE === $project->getStatus() && $project->getUsers()->contains($user));
$hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER') || $project->getUsers()->contains($user);
$hasStatus = Project::TOVOTE === $project->getStatus();
return $hasUser && $hasStatus;
}
private function canMoveToVote(Project $project, User $user): bool
{
return $user->hasRole('ROLE_ADMIN') || (Project::DRAFT === $project->getStatus() && $project->getUsers()->contains($user));
$hasMaster = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasUser = $project->getUsers()->contains($user);
if (Project::VOTED === $project->getStatus()) {
return $hasMaster;
} elseif (Project::DRAFT === $project->getStatus()) {
return $hasMaster || $hasUser;
} else {
return false;
}
}
private function canMoveVoted(Project $project, User $user): bool
{
$hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasStatus = Project::TOVOTE === $project->getStatus() || Project::ARCHIVED === $project->getStatus();
dump($hasStatus);
return $hasUser && $hasStatus;
}
private function canMoveArchived(Project $project, User $user): bool
{
$hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
$hasStatus = Project::VOTED === $project->getStatus();
return $hasUser && $hasStatus;
}
protected function voteOnAttribute(string $attribute, $project, TokenInterface $token): bool
@ -60,6 +98,7 @@ class ProjectVoter extends Voter
if (!$user instanceof User) {
return false;
}
dump($attribute);
return match ($attribute) {
self::VIEW => $this->canView($project, $user),
@ -68,6 +107,8 @@ class ProjectVoter extends Voter
self::DELETE => $this->canDelete($project, $user),
self::MOVEDRAFT => $this->canMoveDraft($project, $user),
self::MOVETOVOTE => $this->canMoveToVote($project, $user),
self::MOVEVOTED => $this->canMoveVoted($project, $user),
self::MOVEARCHIVED => $this->canMoveArchived($project, $user),
default => false,
};
}

View File

@ -4,30 +4,28 @@
{%block body%}
<h2>Projets</h2>
<a href="{{ path('app_user_project_submit') }}" class="btn btn-success mb-3">Ajouter</a>
<div class='d-flex flex-wrap' style='justify-content: left'>
{% for project in projects %}
{% if is_granted('VIEW', project) %}
<div class='card' style='width:300px; margin-right:10px;'>
<div class='card-header d-flex justify-content-between align-items-center'>
{{project.status}}<br>
{{project.title}}
<div>
{% if is_granted('UPDATE', project) %}
<a href="{{ path("app_user_project_update",{id:project.id}) }}" class="btn btn-primary btn-sm"><i class="fas fa-pencil"></i></a>
{% endif %}
<h5>{{project.title}}</h5>
<div>
<a href="{{ path("app_user_project_view",{id:project.id}) }}" class="btn btn-secondary btn-sm"><i class="fas fa-eye"></i></a>
</div>
</div>
<div class='card-body'>
{{project.summary|raw}}
<br>
{{project.summary|nl2br}}
</div>
<div class='card-footer'>
<small><em>
Propriétaires =
<b>Statut</b> = {{ project.status }}<br>
<b>Propriétaires</b> =
{%for user in project.users%}
{{' '~user.username}}
{{loop.first ? user.username : ' - '~user.username}}
{%endfor%}
</em></small>
</div>

View File

@ -0,0 +1,58 @@
{% extends 'base.html.twig' %}
{% block title %} = {{title}}{% endblock %}
{% block body %}
<h1>{{title}}</h1>
{{ form_start(form) }}
{{ form_widget(form.submit) }}
<a href="{{ path(routecancel,{id:idproject}) }}" class="btn btn-secondary me-5">Annuler</a>
{% if mode=="update" %}
<a href="{{ path(routedelete,{idproject:option.project.id,id:option.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
{% endif %}
{% include('include/error.html.twig') %}
<div class="row">
<div class="col-md-4 mx-auto">
<div class="card mt-3">
<div class="card-header">Information</div>
<div class="card-body">
{{ form_row(form.title) }}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-4">
<div class="card mt-3">
<div class="card-header">Information</div>
<div class="card-body">
{{ form_row(form.whyYes) }}
</div>
</div>
</div>
<div class="col-md-4">
<div class="card mt-3">
<div class="card-header">Information</div>
<div class="card-body">
{{ form_row(form.whyNot) }}
</div>
</div>
</div>
</div>
{{ form_end(form) }}
{% endblock %}
{% block localscript %}
<script>
$(document).ready(function() {
$("#project_title").focus();
});
</script>
{% endblock %}

View File

@ -8,18 +8,10 @@
{{ form_start(form) }}
{{ form_widget(form.submit) }}
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
{%if mode=="update" %}
{% if is_granted('MOVEDRAFT', project) %}
<a href="{{ path("app_user_project_move",{id:form.vars.value.id, status:"DRAFT"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
{% endif %}
{% if is_granted('MOVETOVOTE', project) %}
<a href="{{ path("app_user_project_move",{id:form.vars.value.id, status:"TOVOTE"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
{% endif %}
{% if is_granted('DELETE', project) %}
<a href="{{ path(routedelete,{id:form.vars.value.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
{% endif %}
{%endif%}
<a href="{{ path(routecancel) }}" class="btn btn-secondary me-5">Annuler</a>
{% if mode=="update" and is_granted('DELETE', project) %}
<a href="{{ path(routedelete,{id:project.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
{% endif %}
{% include('include/error.html.twig') %}
@ -49,6 +41,34 @@
{% if mode=="update" %}
<div class="col-md-8 mx-auto">
<div class="card mt-3">
<div class="card-header">Options de Vote</div>
<div class="card-body">
<a href="{{ path(routesubmitoption,{idproject:project.id}) }}" class="btn btn-success btn-sm mb-3">Ajouter</a>
<div class="dataTable_wrapper">
<table class="table table-striped table-bordered table-hover" id="dataTables" style="width:100%">
<thead>
<tr>
<th width="100px" class="no-sort">Action</th>
<th>Title</th>
</tr>
</thead>
<tbody>
{% for option in project.options %}
<tr>
<td>
<a href="{{ path(routeupdateoption,{idproject:project.id,id:option.id}) }}" class="me-2"><i class="fas fa-file fa-2x"></i></a>
</td>
<td>{{option.title}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">Description du Projet</div>
<div class="card-body">

View File

@ -0,0 +1,12 @@
{% if is_granted('MOVEDRAFT', project) %}
<a href="{{ path(routemove,{id:project.id, status:"DRAFT"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
{% endif %}
{% if is_granted('MOVETOVOTE', project) %}
<a href="{{ path(routemove,{id:project.id, status:"TOVOTE"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
{% endif %}
{% if is_granted('MOVEVOTED', project) %}
<a href="{{ path(routemove,{id:project.id, status:"VOTED"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le vote ?')">Voter</a>
{% endif %}
{% if is_granted('DELETE', project) %}
<a href="{{ path(routedelete,{id:project.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
{% endif %}

View File

@ -3,29 +3,44 @@
{% block title %} = {{title}}{% endblock %}
{% block body %}
<h1>{{title}}</h1>
<h1 style="display:flex;">
<div style="flex-grow:1">{{title}}</div>
<div class="">{{project.status}}</div>
</h1>
{% if is_granted('UPDATE', project) %}
<a href="{{ path(routeupdate,{id:project.id}) }}" class="btn btn-success me-1">Modifier</a>
{% endif %}
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
<a href="{{ path(routecancel) }}" class="btn btn-secondary me-5">Retour</a>
{% if is_granted('MOVEDRAFT', project) %}
<a href="{{ path("app_user_project_move",{id:project.id, status:"DRAFT"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
<a href="{{ path(routemove,{id:project.id, status:"DRAFT"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre en Brouillon</a>
{% endif %}
{% if is_granted('MOVETOVOTE', project) %}
<a href="{{ path("app_user_project_move",{id:project.id, status:"TOVOTE"}) }}" class="btn btn-primary" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
<a href="{{ path(routemove,{id:project.id, status:"TOVOTE"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le passage au vote ?')">Mettre au Vote</a>
{% endif %}
{% if is_granted('MOVEVOTED', project) %}
<a href="{{ path(routemove,{id:project.id, status:"VOTED"}) }}" class="btn btn-primary me-1" onclick="return confirm('Confirmez-vous le vote ?')">Voter</a>
{% endif %}
{% if is_granted('DELETE', project) %}
<a href="{{ path(routedelete,{id:project.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>
{% endif %}
<div class="row">
<div class="col-md-4 mx-auto">
<div class="card mt-3">
<div class="card-header">Information</div>
<div class="card-body">
{{ project.title }}
{{ project.nature }}
{{ project.summary }}
<b>Titre</b> = {{ project.title }}<br>
<b>Nature</b> = {{ project.nature }}<br><br>
</div>
</div>
<div class="card mt-3">
<div class="card-header">Résumé</div>
<div class="card-body">
{{ project.summary|nl2br }}
</div>
</div>
@ -43,7 +58,7 @@
<div class="card mt-3">
<div class="card-header">Description du Projet</div>
<div class="card-body">
{{ project.description }}
{{ project.description ? project.description|markdown_to_html : "" }}
</div>
</div>
</div>