migration symfo
This commit is contained in:
3
.env
3
.env
@ -18,3 +18,6 @@ CAS_MAIL=mail
|
||||
CAS_LASTNAME=lastname
|
||||
CAS_FIRSTNAME=firstname
|
||||
|
||||
ETHERPAD_URL=http://localhost:9001
|
||||
ETHERPAD_INTERNALURL=http://etherpad:9001
|
||||
ETHERPAD_APIKEY=changeme # identique que data/etherpad/apikey/APIKEY.txt
|
4
.env.dev
4
.env.dev
@ -1,4 +0,0 @@
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_SECRET=628790eb09e2cf96d93a21f4f43433d8
|
||||
###< symfony/framework-bundle ###
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,6 +25,7 @@ phpstan.neon
|
||||
/public/bundles/
|
||||
/var/
|
||||
/vendor/
|
||||
/data/
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> friendsofphp/php-cs-fixer ###
|
||||
|
16
compose.yaml
16
compose.yaml
@ -34,6 +34,22 @@ services:
|
||||
- ./vendor:/app/vendor:delegated
|
||||
- ./public/bundles:/app/public/bundles:delegated
|
||||
|
||||
etherpad:
|
||||
image: etherpad/etherpad:latest
|
||||
container_name: etherpad
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ADMIN_PASSWORD=changeme
|
||||
- TITLE=Mon Etherpad
|
||||
- DEFAULT_PAD_TEXT=Bienvenue dans Etherpad!
|
||||
- API_KEY=changeme
|
||||
- AUTHENTICATION_METHOD=apikey
|
||||
ports:
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- ./volume/etherpad/data:/opt/etherpad-lite/var
|
||||
- ./volume/etherpad/settings:/opt/etherpad-lite/settings
|
||||
- ./volume/etherpad/apikey/APIKEY.txt:/opt/etherpad-lite/APIKEY.txt
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
|
@ -12,7 +12,9 @@ parameters:
|
||||
casMail: "%env(resolve:CAS_MAIL)%"
|
||||
casLastname: "%env(resolve:CAS_LASTNAME)%"
|
||||
casFirstname: "%env(resolve:CAS_FIRSTNAME)%"
|
||||
|
||||
etherpadUrl: "%env(resolve:ETHERPAD_URL)%"
|
||||
etherpadInternalUrl: "%env(resolve:ETHERPAD_INTERNALURL)%"
|
||||
etherpadApiKey: "%env(resolve:ETHERPAD_APIKEY)%"
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
|
@ -2,23 +2,18 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Service\EtherpadService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
#[Route('/', name: 'app_home')]
|
||||
public function home(Request $request): Response
|
||||
public function home(ProjectRepository $projectRepository): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
if (!$user instanceof User) {
|
||||
throw new AccessDeniedException('Vous n\'avez pas accès à cette ressource.');
|
||||
}
|
||||
$projects = $user->getProjects();
|
||||
$projects = $projectRepository->findAll();
|
||||
|
||||
return $this->render('home/home.html.twig', [
|
||||
'usemenu' => true,
|
||||
@ -35,4 +30,19 @@ class HomeController extends AbstractController
|
||||
'usesidebar' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/user/etherpad/{id}', name: 'app_ehterpad')]
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -121,6 +121,9 @@ class ProjectController extends AbstractController
|
||||
$attribute = constant(ProjectVoter::class.'::MOVE'.$status);
|
||||
$this->denyAccessUnlessGranted($attribute, $project);
|
||||
|
||||
if (Project::VOTED == $status) {
|
||||
}
|
||||
|
||||
$project->setStatus(constant(Project::class.'::'.$status));
|
||||
$em->flush();
|
||||
|
||||
|
@ -42,6 +42,15 @@ class Project
|
||||
#[ORM\Column(type: 'datetime', nullable: true)]
|
||||
private ?\DateTimeInterface $dueDate = null;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $nbVoteWhite = 0;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $nbVoteNull = 0;
|
||||
|
||||
#[ORM\Column(type: 'string', nullable: true)]
|
||||
private ?string $resultVote = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
@ -51,14 +60,14 @@ class Project
|
||||
/**
|
||||
* @var Collection<int, ProjectTimeline>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectTimeline::class, cascade: ['remove'])]
|
||||
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectTimeline::class, cascade: ['remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['createdAt' => 'DESC'])]
|
||||
private Collection $timelines;
|
||||
|
||||
/**
|
||||
* @var Collection<int, ProjectTimeline>
|
||||
* @var Collection<int, ProjectOption>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectOption::class, cascade: ['remove'])]
|
||||
#[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectOption::class, cascade: ['remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['title' => 'ASC'])]
|
||||
private Collection $options;
|
||||
|
||||
@ -146,6 +155,42 @@ class Project
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNbVoteWhite(): int
|
||||
{
|
||||
return $this->nbVoteWhite;
|
||||
}
|
||||
|
||||
public function setNbVoteWhite(int $nbVoteWhite): self
|
||||
{
|
||||
$this->nbVoteWhite = $nbVoteWhite;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNbVoteNull(): int
|
||||
{
|
||||
return $this->nbVoteNull;
|
||||
}
|
||||
|
||||
public function setNbVoteNull(int $nbVoteNull): self
|
||||
{
|
||||
$this->nbVoteNull = $nbVoteNull;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getResultVote(): ?string
|
||||
{
|
||||
return $this->resultVote;
|
||||
}
|
||||
|
||||
public function setResultVote(?string $resultVote): self
|
||||
{
|
||||
$this->resultVote = $resultVote;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, User>
|
||||
*/
|
||||
@ -182,4 +227,21 @@ class Project
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function addOption(ProjectOption $option): self
|
||||
{
|
||||
if (!$this->options->contains($option)) {
|
||||
$this->options->add($option);
|
||||
$option->setProject($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeOption(ProjectOption $option): self
|
||||
{
|
||||
$this->options->removeElement($option);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,9 @@ class ProjectOption
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $whyNot = null;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $nbVote = 0;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@ -78,4 +81,16 @@ class ProjectOption
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNbVote(): int
|
||||
{
|
||||
return $this->nbVote;
|
||||
}
|
||||
|
||||
public function setNbVote(int $nbVote): self
|
||||
{
|
||||
$this->nbVote = $nbVote;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use Bnine\MdEditorBundle\Form\Type\MarkdownType;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
@ -28,6 +29,12 @@ class ProjectType extends AbstractType
|
||||
'label' => 'Titre',
|
||||
])
|
||||
|
||||
->add('dueDate', DateType::class, [
|
||||
'label' => 'A Voter pour le',
|
||||
'required' => false,
|
||||
'html5' => true,
|
||||
])
|
||||
|
||||
->add('summary', TextareaType::class, [
|
||||
'label' => 'Résumé',
|
||||
])
|
||||
|
40
src/Form/ProjectVotedType.php
Normal file
40
src/Form/ProjectVotedType.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Form\Type\ProjectOptionType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProjectVotedType 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('options', CollectionType::class, [
|
||||
'entry_type' => ProjectOptionType::class,
|
||||
'entry_options' => ['label' => false],
|
||||
'label' => false,
|
||||
'allow_add' => false,
|
||||
'allow_delete' => false,
|
||||
'by_reference' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Project::class,
|
||||
'mode' => 'submit',
|
||||
]);
|
||||
}
|
||||
}
|
31
src/Form/Type/ProjectOptionType.php
Normal file
31
src/Form/Type/ProjectOptionType.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Entity\ProjectOption;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProjectOptionType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('title', null, [
|
||||
'disabled' => true,
|
||||
'label' => 'Titre',
|
||||
])
|
||||
->add('nbVote', IntegerType::class, [
|
||||
'label' => 'Nombre de votes',
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => ProjectOption::class,
|
||||
]);
|
||||
}
|
||||
}
|
@ -18,10 +18,11 @@ class ProjectVoter extends Voter
|
||||
public const MOVETOVOTE = 'MOVETOVOTE';
|
||||
public const MOVEVOTED = 'MOVEVOTED';
|
||||
public const MOVEARCHIVED = 'MOVEARCHIVED';
|
||||
public const TOME = 'TOME';
|
||||
|
||||
protected function supports(string $attribute, $subject): bool
|
||||
{
|
||||
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE, self::MOVEVOTED, self::MOVEARCHIVED];
|
||||
$attributes = [self::VIEW, self::SUBMIT, self::UPDATE, self::DELETE, self::MOVEDRAFT, self::MOVETOVOTE, self::MOVEVOTED, self::MOVEARCHIVED, self::TOME];
|
||||
|
||||
return in_array($attribute, $attributes) && $subject instanceof Project;
|
||||
}
|
||||
@ -79,7 +80,6 @@ class ProjectVoter extends Voter
|
||||
{
|
||||
$hasUser = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_MASTER');
|
||||
$hasStatus = Project::TOVOTE === $project->getStatus() || Project::ARCHIVED === $project->getStatus();
|
||||
dump($hasStatus);
|
||||
|
||||
return $hasUser && $hasStatus;
|
||||
}
|
||||
@ -92,13 +92,17 @@ class ProjectVoter extends Voter
|
||||
return $hasUser && $hasStatus;
|
||||
}
|
||||
|
||||
private function toMe(Project $project, User $user): bool
|
||||
{
|
||||
return $project->getUsers()->contains($user);
|
||||
}
|
||||
|
||||
protected function voteOnAttribute(string $attribute, $project, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
dump($attribute);
|
||||
|
||||
return match ($attribute) {
|
||||
self::VIEW => $this->canView($project, $user),
|
||||
@ -109,6 +113,7 @@ class ProjectVoter extends Voter
|
||||
self::MOVETOVOTE => $this->canMoveToVote($project, $user),
|
||||
self::MOVEVOTED => $this->canMoveVoted($project, $user),
|
||||
self::MOVEARCHIVED => $this->canMoveArchived($project, $user),
|
||||
self::TOME => $this->toMe($project, $user),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
117
src/Service/EtherpadService.php
Normal file
117
src/Service/EtherpadService.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class EtherpadService
|
||||
{
|
||||
private HttpClientInterface $client;
|
||||
private string $etherpadUrl;
|
||||
private string $etherpadInternalUrl;
|
||||
private string $etherpadApiKey;
|
||||
|
||||
public function __construct(HttpClientInterface $client, ParameterBagInterface $parameter)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->etherpadUrl = $parameter->get('etherpadUrl');
|
||||
$this->etherpadInternalUrl = $parameter->get('etherpadInternalUrl');
|
||||
$this->etherpadApiKey = $parameter->get('etherpadApiKey');
|
||||
}
|
||||
|
||||
public function preparePadAccess(User $user, string $padName): array
|
||||
{
|
||||
// 1. Vérifier/créer l'auteur
|
||||
$authorId = $this->createAuthorIfNeeded($user);
|
||||
|
||||
// 2. Vérifier/créer le groupe lié à ce pad
|
||||
$groupId = $this->createGroupIfNeeded($padName);
|
||||
|
||||
// 3. Créer le pad si pas encore créé
|
||||
$this->createGroupPadIfNeeded($groupId, $padName);
|
||||
|
||||
// 4. Créer une session temporaire (ex: 1h)
|
||||
$validUntil = time() + 3600;
|
||||
$sessionId = $this->createSession($groupId, $authorId, $validUntil);
|
||||
|
||||
// 5. Construire le cookie à ajouter à la réponse
|
||||
$cookie = Cookie::create('sessionID', $sessionId)
|
||||
->withDomain(parse_url($this->etherpadInternalUrl, PHP_URL_HOST))
|
||||
->withPath('/')
|
||||
->withSecure(true)
|
||||
->withHttpOnly(false)
|
||||
->withExpires($validUntil);
|
||||
|
||||
// 6. Construire l’URL iframe
|
||||
$iframeUrl = sprintf(
|
||||
'%s/p/%s?showControls=true&showChat=false&userName=%s',
|
||||
rtrim($this->etherpadUrl, '/'),
|
||||
$padName,
|
||||
urlencode($user->getUsername())
|
||||
);
|
||||
|
||||
return [
|
||||
'iframeUrl' => $iframeUrl,
|
||||
'cookie' => $cookie,
|
||||
];
|
||||
}
|
||||
|
||||
private function createAuthorIfNeeded(User $user): string
|
||||
{
|
||||
// Idéalement, stocker $authorId en base pour éviter de le recréer
|
||||
$response = $this->client->request('GET', $this->etherpadInternalUrl.'/api/1.2.15/createAuthor', [
|
||||
'query' => [
|
||||
'apikey' => $this->etherpadApiKey,
|
||||
'name' => $user->getUsername(),
|
||||
],
|
||||
]);
|
||||
|
||||
$data = $response->toArray(false);
|
||||
|
||||
return $data['data']['authorID'] ?? throw new \RuntimeException('Impossible de créer l\'author');
|
||||
}
|
||||
|
||||
private function createGroupIfNeeded(string $padName): string
|
||||
{
|
||||
// Ici tu peux lier un groupID en base à ton padName
|
||||
$response = $this->client->request('GET', $this->etherpadInternalUrl.'/api/1.2.15/createGroup', [
|
||||
'query' => [
|
||||
'apikey' => $this->etherpadApiKey,
|
||||
],
|
||||
]);
|
||||
|
||||
$data = $response->toArray(false);
|
||||
|
||||
return $data['data']['groupID'] ?? throw new \RuntimeException('Impossible de créer le group');
|
||||
}
|
||||
|
||||
private function createGroupPadIfNeeded(string $groupId, string $padName): void
|
||||
{
|
||||
$this->client->request('GET', $this->etherpadInternalUrl.'/api/1.2.15/createGroupPad', [
|
||||
'query' => [
|
||||
'apikey' => $this->etherpadApiKey,
|
||||
'groupID' => $groupId,
|
||||
'padName' => $padName,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function createSession(string $groupId, string $authorId, int $validUntil): string
|
||||
{
|
||||
$response = $this->client->request('GET', $this->etherpadInternalUrl.'/api/1.2.15/createSession', [
|
||||
'query' => [
|
||||
'apikey' => $this->etherpadApiKey,
|
||||
'groupID' => $groupId,
|
||||
'authorID' => $authorId,
|
||||
'validUntil' => $validUntil,
|
||||
],
|
||||
]);
|
||||
|
||||
$data = $response->toArray(false);
|
||||
|
||||
return $data['data']['sessionID'] ?? throw new \RuntimeException('Impossible de créer la session');
|
||||
}
|
||||
}
|
1
templates/etherpad/show.html.twig
Normal file
1
templates/etherpad/show.html.twig
Normal file
@ -0,0 +1 @@
|
||||
<iframe src="{{ iframeUrl }}" style="width:100%; height:600px; border:0;"></iframe>
|
@ -4,12 +4,44 @@
|
||||
|
||||
{%block body%}
|
||||
<h2>Projets</h2>
|
||||
<a href="{{ path('app_user_project_submit') }}" class="btn btn-success mb-3">Ajouter</a>
|
||||
<ul id="tabProjects" class="nav nav-tabs">
|
||||
<li class="me-5">
|
||||
<a href="{{ path('app_user_project_submit') }}" class="btn btn-success">Ajouter</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="#" data-status="A Voter"">A Voter</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" data-status="Brouillon">Brouillon</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" data-status="Voté">Voté</a>
|
||||
</li>
|
||||
<li class="nav-item ms-auto">
|
||||
<a class="nav-link" href="#" data-status="Archivé">Archivé</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-success nav-tome" href="#">Mes Projets</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class='d-flex flex-wrap' style='justify-content: left'>
|
||||
<div id="containerdraft" data-status="Brouillon" class='containerStatus d-flex flex-wrap mt-3' style='justify-content: left'>
|
||||
<h3 style="width:100%">Brouillon</h3>
|
||||
</div>
|
||||
<div id="containervoted" data-status="A Voter" class='containerStatus d-flex flex-wrap mt-3' style='justify-content: left'>
|
||||
<h3 style="width:100%">A Voter</h3>
|
||||
</div>
|
||||
<div id="containertovote" data-status="Voté" class='containerStatus d-flex flex-wrap mt-3' style='justify-content: left'>
|
||||
<h3 style="width:100%">Voté</h3>
|
||||
</div>
|
||||
<div id="containerarchived" data-status="Archivé" class='containerStatus d-flex flex-wrap mt-3' style='justify-content: left'>
|
||||
<h3 style="width:100%">Archivé</h3>
|
||||
</div>
|
||||
|
||||
<div style="display:none">
|
||||
{% for project in projects %}
|
||||
{% if is_granted('VIEW', project) %}
|
||||
<div class='card' style='width:300px; margin-right:10px;'>
|
||||
<div class='card cardProject' data-status="{{project.status}}" data-tome="{{is_granted('TOME', project)}}" style='width:300px; margin-right:10px;'>
|
||||
<div class='card-header d-flex justify-content-between align-items-center'>
|
||||
<h5>{{project.title}}</h5>
|
||||
|
||||
@ -20,9 +52,12 @@
|
||||
<div class='card-body'>
|
||||
{{project.summary|nl2br}}
|
||||
</div>
|
||||
<div class='card-footer'>
|
||||
<div class='card-footer' style="line-height:14px">
|
||||
<small><em>
|
||||
<b>Statut</b> = {{ project.status }}<br>
|
||||
{% if project.dueDate %}
|
||||
<b>A Voter pour le</b> = {{ project.dueDate|date("d/m/Y") }}<br>
|
||||
{% endif %}
|
||||
<b>Nature</b> = {{ project.nature }}<br>
|
||||
<b>Propriétaires</b> =
|
||||
{%for user in project.users%}
|
||||
{{loop.first ? user.username : ' - '~user.username}}
|
||||
@ -32,7 +67,68 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
{%endblock%}
|
||||
|
||||
{% block localscript %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Cacher tous les containers sauf celui actif au chargement
|
||||
let initialStatus = $('.nav-link.active').data('status');
|
||||
$('.containerStatus').removeClass("d-flex");
|
||||
$('.containerStatus').hide();
|
||||
|
||||
$('.cardProject').each(function () {
|
||||
const $card = $(this);
|
||||
const status = $card.data('status');
|
||||
const $container = $('.containerStatus[data-status="' + status + '"]')
|
||||
|
||||
if ($container.length) {
|
||||
$container.append($card);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(initialStatus);
|
||||
$('.containerStatus[data-status="' + initialStatus + '"]').addClass("d-flex");
|
||||
|
||||
$('#tabProjects .nav-link').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Gère les onglets actifs
|
||||
$('.nav-link').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
if ($(this).hasClass('nav-tome')) {
|
||||
// On masque tt les cardProject
|
||||
$(".cardProject").hide();
|
||||
|
||||
// On affiche que les cardProject avec tome
|
||||
$(".cardProject[data-tome='1']").show();
|
||||
|
||||
$('.containerStatus').each(function () {
|
||||
const $container = $(this);
|
||||
const hasTome = $container.find('.cardProject[data-tome="1"]').length > 0;
|
||||
|
||||
if (hasTome) {
|
||||
$container.addClass('d-flex').show();
|
||||
} else {
|
||||
$container.removeClass('d-flex').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// On affiche tt les cardProject
|
||||
$(".cardProject").show();
|
||||
|
||||
// Récupère le data-status sélectionné
|
||||
let status = $(this).data('status');
|
||||
|
||||
// Cache tous les containers, puis affiche celui correspondant au data-status
|
||||
$('.containerStatus').removeClass('d-flex').hide();
|
||||
$('.containerStatus[data-status="' + status + '"]').addClass("d-flex").show();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -18,9 +18,9 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4 mx-auto">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Information</div>
|
||||
<div class="card-header">Titre</div>
|
||||
<div class="card-body">
|
||||
{{ form_row(form.title) }}
|
||||
{{ form_widget(form.title) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -30,17 +30,17 @@
|
||||
<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-header">Pourquoi Voter Pour</div>
|
||||
<div class="card-body">
|
||||
{{ form_row(form.whyYes) }}
|
||||
{{ form_widget(form.whyYes) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Information</div>
|
||||
<div class="card-header">Pourquoi Voter Contre</div>
|
||||
<div class="card-body">
|
||||
{{ form_row(form.whyNot) }}
|
||||
{{ form_widget(form.whyNot) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -22,6 +22,7 @@
|
||||
<div class="card-body">
|
||||
{{ form_row(form.title) }}
|
||||
{{ form_row(form.nature) }}
|
||||
{{ form_row(form.dueDate) }}
|
||||
{{ form_row(form.users) }}
|
||||
{{ form_row(form.summary) }}
|
||||
</div>
|
||||
@ -51,7 +52,9 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="100px" class="no-sort">Action</th>
|
||||
<th>Title</th>
|
||||
<th>Titre</th>
|
||||
<th width="35%" class="no-sort">Pour</th>
|
||||
<th width="35%" class="no-sort">Contre</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -61,6 +64,8 @@
|
||||
<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>
|
||||
<td>{{option.whyYes?option.whyYes|markdown_to_html:""}}</td>
|
||||
<td>{{option.whyNot?option.whyNot|markdown_to_html:""}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -85,6 +90,13 @@
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("#project_title").focus();
|
||||
|
||||
$('#dataTables').DataTable({
|
||||
columnDefs: [ { "targets": "no-sort", "orderable": false }, { "targets": "no-string", "type" : "num" } ],
|
||||
responsive: true,
|
||||
iDisplayLength: 100,
|
||||
order: [[ 1, "asc" ]]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -1,12 +1 @@
|
||||
{% 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 %}
|
||||
|
||||
|
@ -8,24 +8,50 @@
|
||||
<div class="">{{project.status}}</div>
|
||||
</h1>
|
||||
|
||||
<div class="mb-3">
|
||||
{% 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 me-5">Retour</a>
|
||||
|
||||
{% 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>
|
||||
{% if is_granted('MOVETOVOTE', project) and project.status=="Brouillon"%}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"TOVOTE"}) }}" class="btn btn-primary me-1" onclick="return confirm('Statut = à Voter ?')">
|
||||
<i class="fas fa-angle-double-right"></i> Statut = à Voter
|
||||
</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>
|
||||
{% if is_granted('MOVEVOTED', project) and project.status=="A Voter" %}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"VOTED"}) }}" class="btn btn-primary me-1" onclick="return confirm('Statut = Voté ?')">
|
||||
<i class="fas fa-angle-double-right"></i> Statut = Voté
|
||||
</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>
|
||||
{% if is_granted('MOVEARCHIVED', project) and project.status=="Voté" %}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"ARCHIVED"}) }}" class="btn btn-primary me-1" onclick="return confirm('Statut = Archivé ?')">
|
||||
<i class="fas fa-angle-double-right"></i> Statut = Archivé
|
||||
</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>
|
||||
<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 %}
|
||||
{% if is_granted('MOVEDRAFT', project) and project.status=="A Voter"%}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"DRAFT"}) }}" class="btn btn-warning me-1 float-end" onclick="return confirm('Statut = Brouillon ?')">
|
||||
<i class="fas fa-angle-double-left"></i> Statut = Brouillon
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if is_granted('MOVETOVOTE', project) and project.status=="Voté"%}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"TOVOTE"}) }}" class="btn btn-warning me-1 float-end" onclick="return confirm('Statut = à Voter ?')">
|
||||
<i class="fas fa-angle-double-left"></i> Statut = à Voter
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if is_granted('MOVEVOTED', project) and project.status=="Archivé"%}
|
||||
<a href="{{ path(routemove,{id:project.id, status:"VOTED"}) }}" class="btn btn-warning me-1 float-end" onclick="return confirm('Statut = Voté ?')">
|
||||
<i class="fas fa-angle-double-left"></i> Statut = Voté
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mx-auto">
|
||||
@ -33,7 +59,8 @@
|
||||
<div class="card-header">Information</div>
|
||||
<div class="card-body">
|
||||
<b>Titre</b> = {{ project.title }}<br>
|
||||
<b>Nature</b> = {{ project.nature }}<br><br>
|
||||
<b>Nature</b> = {{ project.nature }}<br>
|
||||
<b>A Voter pour le</b> = {{ project.dueDate ? project.dueDate|date("d/m/Y"):"" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -55,6 +82,23 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-8 mx-auto">
|
||||
<div style="display:flex;" class="mt-3">
|
||||
{% for option in project.options %}
|
||||
<div class="card" style="flex:1 1 0">
|
||||
<center><h2>{{ option.title }}</h2></center>
|
||||
<div style="display:flex;">
|
||||
<div style="flex:auto; padding:10px;">
|
||||
<center><i class="fas fa-plus"></i></center>
|
||||
{{option.whyYes?option.whyYes|markdown_to_html:""}}
|
||||
</div>
|
||||
<div style="flex:auto; padding:10px;">
|
||||
<center><i class="fas fa-minus"></i></center>
|
||||
{{option.whyNot?option.whyNot|markdown_to_html:""}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">Description du Projet</div>
|
||||
<div class="card-body">
|
||||
|
Reference in New Issue
Block a user