svg
This commit is contained in:
34
composer.lock
generated
34
composer.lock
generated
@ -420,16 +420,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/dbal",
|
"name": "doctrine/dbal",
|
||||||
"version": "3.10.0",
|
"version": "3.10.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/dbal.git",
|
"url": "https://github.com/doctrine/dbal.git",
|
||||||
"reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214"
|
"reference": "3626601014388095d3af9de7e9e958623b7ef005"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/1cf840d696373ea0d58ad0a8875c0fadcfc67214",
|
"url": "https://api.github.com/repos/doctrine/dbal/zipball/3626601014388095d3af9de7e9e958623b7ef005",
|
||||||
"reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214",
|
"reference": "3626601014388095d3af9de7e9e958623b7ef005",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -514,7 +514,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/dbal/issues",
|
"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": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -530,7 +530,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-07-10T21:11:04+00:00"
|
"time": "2025-08-05T12:18:06+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/deprecations",
|
"name": "doctrine/deprecations",
|
||||||
@ -1221,16 +1221,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/orm",
|
"name": "doctrine/orm",
|
||||||
"version": "3.5.0",
|
"version": "3.5.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/orm.git",
|
"url": "https://github.com/doctrine/orm.git",
|
||||||
"reference": "6deec3655ba3e8f15280aac11e264225854d2369"
|
"reference": "64444dcfd511089d526cd2c7f74b9d7ed583bdfc"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/orm/zipball/6deec3655ba3e8f15280aac11e264225854d2369",
|
"url": "https://api.github.com/repos/doctrine/orm/zipball/64444dcfd511089d526cd2c7f74b9d7ed583bdfc",
|
||||||
"reference": "6deec3655ba3e8f15280aac11e264225854d2369",
|
"reference": "64444dcfd511089d526cd2c7f74b9d7ed583bdfc",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -1305,9 +1305,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/orm/issues",
|
"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",
|
"name": "doctrine/persistence",
|
||||||
@ -10086,16 +10086,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "2.1.21",
|
"version": "2.1.22",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan.git",
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
|
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4",
|
||||||
"reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
|
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -10140,7 +10140,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-07-28T19:35:08+00:00"
|
"time": "2025-08-04T19:17:37+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-doctrine",
|
"name": "phpstan/phpstan-doctrine",
|
||||||
|
@ -56,12 +56,12 @@ class ProjectController extends AbstractController
|
|||||||
$em->flush();
|
$em->flush();
|
||||||
$this->fileService->init('project', $project->getId());
|
$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', [
|
return $this->render('project/edit.html.twig', [
|
||||||
'usemenu' => true,
|
'usemenu' => true,
|
||||||
'usesidebar' => true,
|
'usesidebar' => $isAdmin,
|
||||||
'title' => 'Création Projet',
|
'title' => 'Création Projet',
|
||||||
'routecancel' => $isAdmin ? 'app_admin_project' : 'app_home',
|
'routecancel' => $isAdmin ? 'app_admin_project' : 'app_home',
|
||||||
'routedelete' => $isAdmin ? 'app_admin_project_delete' : 'app_user_project_delete',
|
'routedelete' => $isAdmin ? 'app_admin_project_delete' : 'app_user_project_delete',
|
||||||
@ -87,7 +87,11 @@ class ProjectController extends AbstractController
|
|||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$em->flush();
|
$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', [
|
return $this->render('project/edit.html.twig', [
|
||||||
@ -95,7 +99,10 @@ class ProjectController extends AbstractController
|
|||||||
'usesidebar' => $isAdmin,
|
'usesidebar' => $isAdmin,
|
||||||
'title' => 'Modification Projet = '.$project->getTitle(),
|
'title' => 'Modification Projet = '.$project->getTitle(),
|
||||||
'routecancel' => $isAdmin ? 'app_admin_project' : 'app_home',
|
'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',
|
'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',
|
'mode' => 'update',
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
'project' => $project,
|
'project' => $project,
|
||||||
@ -119,7 +126,7 @@ class ProjectController extends AbstractController
|
|||||||
|
|
||||||
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
|
$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')]
|
#[Route('/admin/project/delete/{id}', name: 'app_admin_project_delete')]
|
||||||
@ -141,8 +148,6 @@ class ProjectController extends AbstractController
|
|||||||
$em->flush();
|
$em->flush();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->addflash('error', $e->getMessage());
|
$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');
|
return $this->redirectToRoute($isAdmin ? 'app_admin_project' : 'app_home');
|
||||||
@ -158,11 +163,16 @@ class ProjectController extends AbstractController
|
|||||||
|
|
||||||
$this->denyAccessUnlessGranted(ProjectVoter::VIEW, $project);
|
$this->denyAccessUnlessGranted(ProjectVoter::VIEW, $project);
|
||||||
|
|
||||||
|
$isAdmin = str_starts_with($request->attributes->get('_route'), 'app_admin');
|
||||||
|
|
||||||
return $this->render('project/view.html.twig', [
|
return $this->render('project/view.html.twig', [
|
||||||
'usemenu' => true,
|
'usemenu' => true,
|
||||||
'usesidebar' => false,
|
'usesidebar' => false,
|
||||||
'title' => 'Projet = '.$project->getTitle(),
|
'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,
|
'project' => $project,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
111
src/Controller/ProjectOptionController.php
Normal file
111
src/Controller/ProjectOptionController.php
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
@ -55,10 +55,18 @@ class Project
|
|||||||
#[ORM\OrderBy(['createdAt' => 'DESC'])]
|
#[ORM\OrderBy(['createdAt' => 'DESC'])]
|
||||||
private Collection $timelines;
|
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()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->users = new ArrayCollection();
|
$this->users = new ArrayCollection();
|
||||||
$this->timelines = new ArrayCollection();
|
$this->timelines = new ArrayCollection();
|
||||||
|
$this->options = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@ -169,4 +177,9 @@ class Project
|
|||||||
{
|
{
|
||||||
return $this->timelines;
|
return $this->timelines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getOptions(): Collection
|
||||||
|
{
|
||||||
|
return $this->options;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
81
src/Entity/ProjectOption.php
Normal file
81
src/Entity/ProjectOption.php
Normal 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
45
src/Form/OptionType.php
Normal 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',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ class ProjectType extends AbstractType
|
|||||||
$builder
|
$builder
|
||||||
->add('submit', SubmitType::class, [
|
->add('submit', SubmitType::class, [
|
||||||
'label' => 'Valider',
|
'label' => 'Valider',
|
||||||
'attr' => ['class' => 'btn btn-success no-print'],
|
'attr' => ['class' => 'btn btn-success no-print me-1'],
|
||||||
])
|
])
|
||||||
|
|
||||||
->add('title', TextType::class, [
|
->add('title', TextType::class, [
|
||||||
|
@ -24,7 +24,7 @@ class UserType extends AbstractType
|
|||||||
$builder
|
$builder
|
||||||
->add('submit', SubmitType::class, [
|
->add('submit', SubmitType::class, [
|
||||||
'label' => 'Valider',
|
'label' => 'Valider',
|
||||||
'attr' => ['class' => 'btn btn-success no-print'],
|
'attr' => ['class' => 'btn btn-success no-print me-1'],
|
||||||
])
|
])
|
||||||
|
|
||||||
->add('username', TextType::class, [
|
->add('username', TextType::class, [
|
||||||
|
18
src/Repository/ProjectOptionRepository.php
Normal file
18
src/Repository/ProjectOptionRepository.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -16,10 +16,12 @@ class ProjectVoter extends Voter
|
|||||||
public const DELETE = 'DELETE';
|
public const DELETE = 'DELETE';
|
||||||
public const MOVEDRAFT = 'MOVEDRAFT';
|
public const MOVEDRAFT = 'MOVEDRAFT';
|
||||||
public const MOVETOVOTE = 'MOVETOVOTE';
|
public const MOVETOVOTE = 'MOVETOVOTE';
|
||||||
|
public const MOVEVOTED = 'MOVEVOTED';
|
||||||
|
public const MOVEARCHIVED = 'MOVEARCHIVED';
|
||||||
|
|
||||||
protected function supports(string $attribute, $subject): bool
|
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;
|
return in_array($attribute, $attributes) && $subject instanceof Project;
|
||||||
}
|
}
|
||||||
@ -36,22 +38,58 @@ class ProjectVoter extends Voter
|
|||||||
|
|
||||||
private function canUpdate(Project $project, User $user): bool
|
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
|
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
|
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
|
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
|
protected function voteOnAttribute(string $attribute, $project, TokenInterface $token): bool
|
||||||
@ -60,6 +98,7 @@ class ProjectVoter extends Voter
|
|||||||
if (!$user instanceof User) {
|
if (!$user instanceof User) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
dump($attribute);
|
||||||
|
|
||||||
return match ($attribute) {
|
return match ($attribute) {
|
||||||
self::VIEW => $this->canView($project, $user),
|
self::VIEW => $this->canView($project, $user),
|
||||||
@ -68,6 +107,8 @@ class ProjectVoter extends Voter
|
|||||||
self::DELETE => $this->canDelete($project, $user),
|
self::DELETE => $this->canDelete($project, $user),
|
||||||
self::MOVEDRAFT => $this->canMoveDraft($project, $user),
|
self::MOVEDRAFT => $this->canMoveDraft($project, $user),
|
||||||
self::MOVETOVOTE => $this->canMoveToVote($project, $user),
|
self::MOVETOVOTE => $this->canMoveToVote($project, $user),
|
||||||
|
self::MOVEVOTED => $this->canMoveVoted($project, $user),
|
||||||
|
self::MOVEARCHIVED => $this->canMoveArchived($project, $user),
|
||||||
default => false,
|
default => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,30 +4,28 @@
|
|||||||
|
|
||||||
{%block body%}
|
{%block body%}
|
||||||
<h2>Projets</h2>
|
<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'>
|
<div class='d-flex flex-wrap' style='justify-content: left'>
|
||||||
{% for project in projects %}
|
{% for project in projects %}
|
||||||
{% if is_granted('VIEW', project) %}
|
{% if is_granted('VIEW', project) %}
|
||||||
<div class='card' style='width:300px; margin-right:10px;'>
|
<div class='card' style='width:300px; margin-right:10px;'>
|
||||||
<div class='card-header d-flex justify-content-between align-items-center'>
|
<div class='card-header d-flex justify-content-between align-items-center'>
|
||||||
{{project.status}}<br>
|
<h5>{{project.title}}</h5>
|
||||||
{{project.title}}
|
|
||||||
|
|
||||||
<div>
|
<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 %}
|
|
||||||
|
|
||||||
<a href="{{ path("app_user_project_view",{id:project.id}) }}" class="btn btn-secondary btn-sm"><i class="fas fa-eye"></i></a>
|
<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>
|
</div>
|
||||||
<div class='card-body'>
|
<div class='card-body'>
|
||||||
{{project.summary|raw}}
|
{{project.summary|nl2br}}
|
||||||
|
</div>
|
||||||
<br>
|
<div class='card-footer'>
|
||||||
<small><em>
|
<small><em>
|
||||||
Propriétaires =
|
<b>Statut</b> = {{ project.status }}<br>
|
||||||
|
<b>Propriétaires</b> =
|
||||||
{%for user in project.users%}
|
{%for user in project.users%}
|
||||||
{{' '~user.username}}
|
{{loop.first ? user.username : ' - '~user.username}}
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
</em></small>
|
</em></small>
|
||||||
</div>
|
</div>
|
||||||
|
58
templates/option/edit.html.twig
Normal file
58
templates/option/edit.html.twig
Normal 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 %}
|
@ -8,18 +8,10 @@
|
|||||||
|
|
||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
{{ form_widget(form.submit) }}
|
{{ form_widget(form.submit) }}
|
||||||
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
|
<a href="{{ path(routecancel) }}" class="btn btn-secondary me-5">Annuler</a>
|
||||||
{%if mode=="update" %}
|
{% if mode=="update" and is_granted('DELETE', project) %}
|
||||||
{% if is_granted('MOVEDRAFT', 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>
|
||||||
<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 %}
|
{% 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%}
|
|
||||||
|
|
||||||
{% include('include/error.html.twig') %}
|
{% include('include/error.html.twig') %}
|
||||||
|
|
||||||
@ -49,6 +41,34 @@
|
|||||||
|
|
||||||
{% if mode=="update" %}
|
{% if mode=="update" %}
|
||||||
<div class="col-md-8 mx-auto">
|
<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 mt-3">
|
||||||
<div class="card-header">Description du Projet</div>
|
<div class="card-header">Description du Projet</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
12
templates/project/status.html.twig
Normal file
12
templates/project/status.html.twig
Normal 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 %}
|
@ -3,16 +3,25 @@
|
|||||||
{% block title %} = {{title}}{% endblock %}
|
{% block title %} = {{title}}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% 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) %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% if is_granted('MOVETOVOTE', project) %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% if is_granted('DELETE', project) %}
|
{% 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>
|
<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>
|
||||||
@ -23,9 +32,15 @@
|
|||||||
<div class="card mt-3">
|
<div class="card mt-3">
|
||||||
<div class="card-header">Information</div>
|
<div class="card-header">Information</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{{ project.title }}
|
<b>Titre</b> = {{ project.title }}<br>
|
||||||
{{ project.nature }}
|
<b>Nature</b> = {{ project.nature }}<br><br>
|
||||||
{{ project.summary }}
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">Résumé</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{{ project.summary|nl2br }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -43,7 +58,7 @@
|
|||||||
<div class="card mt-3">
|
<div class="card mt-3">
|
||||||
<div class="card-header">Description du Projet</div>
|
<div class="card-header">Description du Projet</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{{ project.description }}
|
{{ project.description ? project.description|markdown_to_html : "" }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user