mise à jour de ma branche

This commit is contained in:
Yacha-Shan 2025-05-05 10:07:45 +02:00
commit c640bdc4d3
55 changed files with 1295 additions and 608 deletions

4
.idea/dataSources.xml generated
View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true"> <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="hegreetconfort@localhost" uuid="21423ae4-3232-4641-affb-06399f70655a"> <data-source source="LOCAL" name="HegreEtConfort@localhost" uuid="21423ae4-3232-4641-affb-06399f70655a">
<driver-ref>postgresql</driver-ref> <driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize> <synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver> <jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/hegreetconfort</jdbc-url> <jdbc-url>jdbc:postgresql://localhost:5433/HegreEtConfort</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir> <working-dir>$ProjectFileDir$</working-dir>
</data-source> </data-source>
</component> </component>

15
.idea/php.xml generated
View File

@ -10,6 +10,11 @@
<option name="highlightLevel" value="WARNING" /> <option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" /> <option name="transferred" value="true" />
</component> </component>
<component name="PhpCodeSniffer">
<phpcs_settings>
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="4bd9484f-d78e-4315-b899-c673e4a83c70" timeout="30000" />
</phpcs_settings>
</component>
<component name="PhpIncludePathManager"> <component name="PhpIncludePathManager">
<include_path> <include_path>
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" /> <path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
@ -146,6 +151,11 @@
</include_path> </include_path>
</component> </component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.2" /> <component name="PhpProjectSharedConfiguration" php_language_level="8.2" />
<component name="PhpStan">
<PhpStan_settings>
<phpstan_by_interpreter asDefaultInterpreter="true" interpreter_id="4bd9484f-d78e-4315-b899-c673e4a83c70" timeout="60000" />
</PhpStan_settings>
</component>
<component name="PhpStanOptionsConfiguration"> <component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" /> <option name="transferred" value="true" />
</component> </component>
@ -154,6 +164,11 @@
<PhpUnitSettings configuration_file_path="$PROJECT_DIR$/phpunit.xml.dist" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" use_configuration_file="true" /> <PhpUnitSettings configuration_file_path="$PROJECT_DIR$/phpunit.xml.dist" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" use_configuration_file="true" />
</phpunit_settings> </phpunit_settings>
</component> </component>
<component name="Psalm">
<Psalm_settings>
<psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="4bd9484f-d78e-4315-b899-c673e4a83c70" timeout="60000" />
</Psalm_settings>
</component>
<component name="PsalmOptionsConfiguration"> <component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" /> <option name="transferred" value="true" />
</component> </component>

59
README.md Normal file
View File

@ -0,0 +1,59 @@
# 🚀 Application de Gestion Chauffagiste
## Description
Cette application permet de gérer les interventions, les utilisateurs (chauffagistes, secrétaires, admin), les pièces détachées, les véhicules, et les plannings dans une entreprise de chauffagistes.
## Fonctionnalités principales
- **Gestion des utilisateurs** : création de chauffagistes, secrétaires, et admins avec des rôles spécifiques.
- **Gestion des interventions** : assignation des chauffagistes, suivi des véhicules et des pièces détachées.
- **Gestion des stocks et des véhicules** : gestion des pièces détachées et des véhicules.
- **Planning des interventions** : chaque rôle a accès à un planning adapté (chauffagiste, secrétaire, admin).
- **Sécurisation par rôles** : accès aux pages limité par rôle (Admin, Secrétaire, Chauffagiste).
### Précisions pour les rôles :
- **Admin** : Accès complet à toutes les fonctionnalités (gestion des utilisateurs, véhicules, stocks, plannings, etc.)
- **Secrétaire** : Peut gérer les chauffagistes, les interventions et le planning des chauffagistes, mais **ne peut pas gérer d'autres secrétaires ni les admins**.
- **Chauffagiste** : Accède uniquement à ses propres interventions et à son planning.
## Installation
### Prérequis :
- PHP 8.x
- Composer
- Symfony 7.x
### Étapes d'installation :
1. Clonez le projet :
```bash
git clone https://gitea.btssio-poitiers.fr/sermandm/HegreEtConfort.git
```
2. Installez les dépendances avec Composer :
```bash
cd chauffagiste-app
composer install
```
3. Créez la base de données :
```bash
php bin/console doctrine:database:create
```
4. Exécutez les migrations pour créer les tables :
```bash
php bin/console doctrine:migrations:migrate
```
5. Lancez le serveur Symfony :
```bash
symfony server:start
```
Accédez ensuite à l'application sur `http://localhost:8000`.
## Sécurisation des accès
Les secrétaires ont accès à toutes les pages **sauf celles concernant d'autres secrétaires et les admins**. Cela est géré par les contrôleurs via la méthode `denyAccessUnlessGranted()` pour vérifier le rôle de l'utilisateur. Par exemple, un secrétaire ne pourra pas modifier un autre secrétaire.
---
## Technologies utilisées
- Symfony 7.x
- Doctrine ORM
- Twig
- PHP 8.x

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250425115854 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE intervention_stock (intervention_id INT NOT NULL, stock_id INT NOT NULL, PRIMARY KEY(intervention_id, stock_id))');
$this->addSql('CREATE INDEX IDX_3E534AE48EAE3863 ON intervention_stock (intervention_id)');
$this->addSql('CREATE INDEX IDX_3E534AE4DCD6110 ON intervention_stock (stock_id)');
$this->addSql('CREATE TABLE user_skill (utilisateur_id INT NOT NULL, skill_id INT NOT NULL, PRIMARY KEY(utilisateur_id, skill_id))');
$this->addSql('CREATE INDEX IDX_BCFF1F2FFB88E14F ON user_skill (utilisateur_id)');
$this->addSql('CREATE INDEX IDX_BCFF1F2F5585C142 ON user_skill (skill_id)');
$this->addSql('ALTER TABLE intervention_stock ADD CONSTRAINT FK_3E534AE48EAE3863 FOREIGN KEY (intervention_id) REFERENCES intervention (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE intervention_stock ADD CONSTRAINT FK_3E534AE4DCD6110 FOREIGN KEY (stock_id) REFERENCES stock (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE user_skill ADD CONSTRAINT FK_BCFF1F2FFB88E14F FOREIGN KEY (utilisateur_id) REFERENCES utilisateur (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE user_skill ADD CONSTRAINT FK_BCFF1F2F5585C142 FOREIGN KEY (skill_id) REFERENCES skill (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE intervention ADD user_id INT NOT NULL');
$this->addSql('ALTER TABLE intervention ADD fault_id INT NOT NULL');
$this->addSql('ALTER TABLE intervention ADD vehicle_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE intervention ADD CONSTRAINT FK_D11814ABA76ED395 FOREIGN KEY (user_id) REFERENCES utilisateur (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE intervention ADD CONSTRAINT FK_D11814AB24171CD3 FOREIGN KEY (fault_id) REFERENCES fault (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE intervention ADD CONSTRAINT FK_D11814AB545317D1 FOREIGN KEY (vehicle_id) REFERENCES vehicle (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_D11814ABA76ED395 ON intervention (user_id)');
$this->addSql('CREATE INDEX IDX_D11814AB24171CD3 ON intervention (fault_id)');
$this->addSql('CREATE INDEX IDX_D11814AB545317D1 ON intervention (vehicle_id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE intervention_stock DROP CONSTRAINT FK_3E534AE48EAE3863');
$this->addSql('ALTER TABLE intervention_stock DROP CONSTRAINT FK_3E534AE4DCD6110');
$this->addSql('ALTER TABLE user_skill DROP CONSTRAINT FK_BCFF1F2FFB88E14F');
$this->addSql('ALTER TABLE user_skill DROP CONSTRAINT FK_BCFF1F2F5585C142');
$this->addSql('DROP TABLE intervention_stock');
$this->addSql('DROP TABLE user_skill');
$this->addSql('ALTER TABLE intervention DROP CONSTRAINT FK_D11814ABA76ED395');
$this->addSql('ALTER TABLE intervention DROP CONSTRAINT FK_D11814AB24171CD3');
$this->addSql('ALTER TABLE intervention DROP CONSTRAINT FK_D11814AB545317D1');
$this->addSql('DROP INDEX IDX_D11814ABA76ED395');
$this->addSql('DROP INDEX IDX_D11814AB24171CD3');
$this->addSql('DROP INDEX IDX_D11814AB545317D1');
$this->addSql('ALTER TABLE intervention DROP user_id');
$this->addSql('ALTER TABLE intervention DROP fault_id');
$this->addSql('ALTER TABLE intervention DROP vehicle_id');
}
}

View File

@ -12,10 +12,10 @@ class AuthenticationController extends AbstractController
#[Route(path: '/', name: 'app_login')] #[Route(path: '/', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response public function login(AuthenticationUtils $authenticationUtils): Response
{ {
// get the login error if there is one // Get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError(); $error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user // Last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername(); $lastUsername = $authenticationUtils->getLastUsername();
return $this->render('login/index.html.twig', [ return $this->render('login/index.html.twig', [

View File

@ -2,43 +2,89 @@
namespace App\Controller; namespace App\Controller;
use App\Repository\InterventionRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Annotation\Route;
#[Route('/calendrier')] #[Route('/calendrier')]
class CalendrierController extends AbstractController class CalendrierController extends AbstractController
{ {
#[Route(name: 'app_calendrier_index', methods: ['GET'])] #[Route('/chauffagiste', name: 'app_calendrier_indexChauffagiste')]
public function index(): Response public function indexChauffagiste(InterventionRepository $interventionRepository): Response
{ {
return $this->render('calendrier/index.html.twig', [ // Vérification : seul le chauffagiste connecté peut voir son propre calendrier
'controller_name' => 'CalendrierController', $this->denyAccessUnlessGranted('ROLE_CHAUFFAGISTE');
// Récupérer les interventions du chauffagiste connecté
$interventions = $interventionRepository->findByUser($this->getUser());
// Préparer les événements pour FullCalendar
$events = [];
foreach ($interventions as $intervention) {
$events[] = [
'title' => $intervention->getTitle(),
'start' => $intervention->getStartDate()->format('Y-m-d H:i:s'),
'end' => $intervention->getEndDate()->format('Y-m-d H:i:s'),
'description' => $intervention->getDescription(),
];
}
// Passer les événements à la vue
return $this->render('calendrier/indexChauffagiste.html.twig', [
'events' => json_encode($events),
]); ]);
} }
#[Route('/calendrier/secretaire', name: 'calendrier_index_secretaire')] #[Route('/secretaire', name: 'app_calendrier_indexSecretaire')]
public function indexSecretaire(): Response public function indexSecretaire(InterventionRepository $interventionRepository): Response
{ {
return $this->render('calendrier/indexSecretaire.html.twig'); // Vérification : seul le secrétaire peut accéder à ce calendrier
$this->denyAccessUnlessGranted('ROLE_SECRETAIRE');
// Récupérer toutes les interventions de tous les chauffagistes
$interventions = $interventionRepository->findAll();
// Préparer les événements pour FullCalendar
$events = [];
foreach ($interventions as $intervention) {
$events[] = [
'title' => $intervention->getTitle(),
'start' => $intervention->getStartDate()->format('Y-m-d H:i:s'),
'end' => $intervention->getEndDate()->format('Y-m-d H:i:s'),
'description' => $intervention->getDescription(),
];
}
// Passer les événements à la vue
return $this->render('calendrier/indexSecretaire.html.twig', [
'events' => json_encode($events),
]);
} }
#[Route('/calendrier/chauffagiste', name: 'calendrier_index_chauffagiste')] #[Route('/admin', name: 'app_calendrier_index')]
public function indexChauffagiste(): Response public function indexAdmin(InterventionRepository $interventionRepository): Response
{ {
return $this->render('calendrier/indexChauffagiste.html.twig'); // Vérification : seul un admin peut accéder à ce calendrier
$this->denyAccessUnlessGranted('ROLE_ADMIN');
// Récupérer toutes les interventions de tous les chauffagistes
$interventions = $interventionRepository->findAll();
// Préparer les événements pour FullCalendar
$events = [];
foreach ($interventions as $intervention) {
$events[] = [
'title' => $intervention->getTitle(),
'start' => $intervention->getStartDate()->format('Y-m-d H:i:s'),
'end' => $intervention->getEndDate()->format('Y-m-d H:i:s'),
'description' => $intervention->getDescription(),
];
}
// Passer les événements à la vue
return $this->render('calendrier/index.html.twig', [
'events' => json_encode($events),
]);
} }
// créer intervention : secretaire + admin
// créer un nouveau rdv
// modifier un rdv
// supprimer un rdv
// choisir un chauffagiste
// deconnexion
// créer intervention : chauffagiste
// modifier un rdv
// deconnexion
// quand on créer une rdv, alors cela nous redirige vers la page intervention
} }

View File

@ -4,26 +4,28 @@ namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Attribute\Route;
class DashboardController extends AbstractController class DashboardController extends AbstractController
{ {
#[Route('/admin/dashboard', name: 'admin_dashboard')] #[Route('/admin/dashboard', name: 'admin_dashboard')]
public function admin(): Response public function admin(): Response
{ {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
return $this->render('dashboard/admin.html.twig'); return $this->render('dashboard/admin.html.twig');
} }
#[Route('/secretaire/dashboard', name: 'secretaire_dashboard')] #[Route('/secretaire/dashboard', name: 'secretaire_dashboard')]
public function secretaire(): Response public function secretaire(): Response
{ {
$this->denyAccessUnlessGranted('ROLE_SECRETAIRE');
return $this->render('dashboard/secretaire.html.twig'); return $this->render('dashboard/secretaire.html.twig');
} }
#[Route('/chauffagiste/dashboard', name: 'chauffagiste_dashboard')] #[Route('/chauffagiste/dashboard', name: 'chauffagiste_dashboard')]
public function chauffagiste(): Response public function chauffagiste(): Response
{ {
$this->denyAccessUnlessGranted('ROLE_CHAUFFAGISTE');
return $this->render('dashboard/chauffagiste.html.twig'); return $this->render('dashboard/chauffagiste.html.twig');
} }
} }

View File

@ -17,8 +17,13 @@ final class FaultController extends AbstractController
#[Route(name: 'app_fault_index', methods: ['GET'])] #[Route(name: 'app_fault_index', methods: ['GET'])]
public function index(FaultRepository $faultRepository): Response public function index(FaultRepository $faultRepository): Response
{ {
// Filtrage des pannes : un chauffagiste ne peut voir que ses pannes
$faults = $this->isGranted('ROLE_CHAUFFAGISTE')
? $faultRepository->findByUser($this->getUser()) // Filtre les pannes par utilisateur
: $faultRepository->findAll(); // Admins voient toutes les pannes
return $this->render('fault/index.html.twig', [ return $this->render('fault/index.html.twig', [
'faults' => $faultRepository->findAll(), 'faults' => $faults,
]); ]);
} }
@ -30,6 +35,11 @@ final class FaultController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
// Associe la panne à un chauffagiste si c'est un chauffagiste
if ($this->isGranted('ROLE_CHAUFFAGISTE')) {
$fault->setUser($this->getUser());
}
$entityManager->persist($fault); $entityManager->persist($fault);
$entityManager->flush(); $entityManager->flush();
@ -45,6 +55,11 @@ final class FaultController extends AbstractController
#[Route('/{id}', name: 'app_fault_show', methods: ['GET'])] #[Route('/{id}', name: 'app_fault_show', methods: ['GET'])]
public function show(Fault $fault): Response public function show(Fault $fault): Response
{ {
// Un chauffagiste ne peut voir que ses pannes
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $fault->getUser() !== $this->getUser()) {
throw $this->createAccessDeniedException('Vous ne pouvez pas voir cette panne.');
}
return $this->render('fault/show.html.twig', [ return $this->render('fault/show.html.twig', [
'fault' => $fault, 'fault' => $fault,
]); ]);
@ -53,6 +68,11 @@ final class FaultController extends AbstractController
#[Route('/{id}/edit', name: 'app_fault_edit', methods: ['GET', 'POST'])] #[Route('/{id}/edit', name: 'app_fault_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response public function edit(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response
{ {
// Un chauffagiste ne peut modifier que ses propres pannes
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $fault->getUser() !== $this->getUser()) {
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier cette panne.');
}
$form = $this->createForm(FaultType::class, $fault); $form = $this->createForm(FaultType::class, $fault);
$form->handleRequest($request); $form->handleRequest($request);
@ -71,7 +91,12 @@ final class FaultController extends AbstractController
#[Route('/{id}', name: 'app_fault_delete', methods: ['POST'])] #[Route('/{id}', name: 'app_fault_delete', methods: ['POST'])]
public function delete(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response public function delete(Request $request, Fault $fault, EntityManagerInterface $entityManager): Response
{ {
if ($this->isCsrfTokenValid('delete'.$fault->getId(), $request->getPayload()->getString('_token'))) { // Un chauffagiste ne peut supprimer que ses propres pannes
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $fault->getUser() !== $this->getUser()) {
throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer cette panne.');
}
if ($this->isCsrfTokenValid('delete'.$fault->getId(), $request->get('csrf_token'))) {
$entityManager->remove($fault); $entityManager->remove($fault);
$entityManager->flush(); $entityManager->flush();
} }

View File

@ -17,8 +17,16 @@ final class InterventionController extends AbstractController
#[Route(name: 'app_intervention_index', methods: ['GET'])] #[Route(name: 'app_intervention_index', methods: ['GET'])]
public function index(InterventionRepository $interventionRepository): Response public function index(InterventionRepository $interventionRepository): Response
{ {
// Vérifier si l'utilisateur est un chauffagiste, pour filtrer ses interventions
if ($this->isGranted('ROLE_CHAUFFAGISTE')) {
$interventions = $interventionRepository->findByUser($this->getUser()); // On filtre par utilisateur connecté
} else {
// Les autres rôles (admin) peuvent voir toutes les interventions
$interventions = $interventionRepository->findAll();
}
return $this->render('intervention/index.html.twig', [ return $this->render('intervention/index.html.twig', [
'interventions' => $interventionRepository->findAll(), 'interventions' => $interventions,
]); ]);
} }
@ -30,6 +38,11 @@ final class InterventionController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
// Si l'utilisateur est un chauffagiste, on associe l'intervention à lui
if ($this->isGranted('ROLE_CHAUFFAGISTE')) {
$intervention->setUser($this->getUser());
}
$entityManager->persist($intervention); $entityManager->persist($intervention);
$entityManager->flush(); $entityManager->flush();
@ -45,6 +58,11 @@ final class InterventionController extends AbstractController
#[Route('/{id}', name: 'app_intervention_show', methods: ['GET'])] #[Route('/{id}', name: 'app_intervention_show', methods: ['GET'])]
public function show(Intervention $intervention): Response public function show(Intervention $intervention): Response
{ {
// Vérifier si l'utilisateur peut voir cette intervention (chauffagiste ne voit que ses interventions)
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $intervention->getUser() !== $this->getUser()) {
throw $this->createAccessDeniedException('Vous ne pouvez pas voir cette intervention.');
}
return $this->render('intervention/show.html.twig', [ return $this->render('intervention/show.html.twig', [
'intervention' => $intervention, 'intervention' => $intervention,
]); ]);
@ -53,6 +71,11 @@ final class InterventionController extends AbstractController
#[Route('/{id}/edit', name: 'app_intervention_edit', methods: ['GET', 'POST'])] #[Route('/{id}/edit', name: 'app_intervention_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response public function edit(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response
{ {
// Vérification de sécurité : un chauffagiste ne peut modifier que ses propres interventions
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $intervention->getUser() !== $this->getUser()) {
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier cette intervention.');
}
$form = $this->createForm(InterventionType::class, $intervention); $form = $this->createForm(InterventionType::class, $intervention);
$form->handleRequest($request); $form->handleRequest($request);
@ -71,7 +94,12 @@ final class InterventionController extends AbstractController
#[Route('/{id}', name: 'app_intervention_delete', methods: ['POST'])] #[Route('/{id}', name: 'app_intervention_delete', methods: ['POST'])]
public function delete(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response public function delete(Request $request, Intervention $intervention, EntityManagerInterface $entityManager): Response
{ {
if ($this->isCsrfTokenValid('delete'.$intervention->getId(), $request->getPayload()->getString('_token'))) { // Vérification de sécurité : un chauffagiste ne peut supprimer que ses propres interventions
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $intervention->getUser() !== $this->getUser()) {
throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer cette intervention.');
}
if ($this->isCsrfTokenValid('delete'.$intervention->getId(), $request->get('csrf_token'))) {
$entityManager->remove($intervention); $entityManager->remove($intervention);
$entityManager->flush(); $entityManager->flush();
} }

View File

@ -17,6 +17,7 @@ final class SkillController extends AbstractController
#[Route(name: 'app_skill_index', methods: ['GET'])] #[Route(name: 'app_skill_index', methods: ['GET'])]
public function index(SkillRepository $skillRepository): Response public function index(SkillRepository $skillRepository): Response
{ {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
return $this->render('skill/admin.html.twig', [ return $this->render('skill/admin.html.twig', [
'skills' => $skillRepository->findAll(), 'skills' => $skillRepository->findAll(),
]); ]);
@ -25,6 +26,7 @@ final class SkillController extends AbstractController
#[Route('/new', name: 'app_skill_new', methods: ['GET', 'POST'])] #[Route('/new', name: 'app_skill_new', methods: ['GET', 'POST'])]
public function new(Request $request, EntityManagerInterface $entityManager): Response public function new(Request $request, EntityManagerInterface $entityManager): Response
{ {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$skill = new Skill(); $skill = new Skill();
$form = $this->createForm(SkillType::class, $skill); $form = $this->createForm(SkillType::class, $skill);
$form->handleRequest($request); $form->handleRequest($request);
@ -53,6 +55,7 @@ final class SkillController extends AbstractController
#[Route('/{id}/edit', name: 'app_skill_edit', methods: ['GET', 'POST'])] #[Route('/{id}/edit', name: 'app_skill_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response public function edit(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response
{ {
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$form = $this->createForm(SkillType::class, $skill); $form = $this->createForm(SkillType::class, $skill);
$form->handleRequest($request); $form->handleRequest($request);
@ -71,7 +74,8 @@ final class SkillController extends AbstractController
#[Route('/{id}', name: 'app_skill_delete', methods: ['POST'])] #[Route('/{id}', name: 'app_skill_delete', methods: ['POST'])]
public function delete(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response public function delete(Request $request, Skill $skill, EntityManagerInterface $entityManager): Response
{ {
if ($this->isCsrfTokenValid('delete'.$skill->getId(), $request->getPayload()->getString('_token'))) { $this->denyAccessUnlessGranted('ROLE_ADMIN');
if ($this->isCsrfTokenValid('delete'.$skill->getId(), $request->get('csrf_token'))) {
$entityManager->remove($skill); $entityManager->remove($skill);
$entityManager->flush(); $entityManager->flush();
} }

View File

@ -53,6 +53,11 @@ final class StockController extends AbstractController
#[Route('/{id}/edit', name: 'app_stock_edit', methods: ['GET', 'POST'])] #[Route('/{id}/edit', name: 'app_stock_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Stock $stock, EntityManagerInterface $entityManager): Response public function edit(Request $request, Stock $stock, EntityManagerInterface $entityManager): Response
{ {
// Si un chauffagiste essaie de modifier un stock d'admin
if ($this->isGranted('ROLE_CHAUFFAGISTE')) {
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier ce stock.');
}
$form = $this->createForm(StockType::class, $stock); $form = $this->createForm(StockType::class, $stock);
$form->handleRequest($request); $form->handleRequest($request);
@ -71,7 +76,7 @@ final class StockController extends AbstractController
#[Route('/{id}', name: 'app_stock_delete', methods: ['POST'])] #[Route('/{id}', name: 'app_stock_delete', methods: ['POST'])]
public function delete(Request $request, Stock $stock, EntityManagerInterface $entityManager): Response public function delete(Request $request, Stock $stock, EntityManagerInterface $entityManager): Response
{ {
if ($this->isCsrfTokenValid('delete'.$stock->getId(), $request->getPayload()->getString('_token'))) { if ($this->isCsrfTokenValid('delete'.$stock->getId(), $request->get('csrf_token'))) {
$entityManager->remove($stock); $entityManager->remove($stock);
$entityManager->flush(); $entityManager->flush();
} }

View File

@ -10,12 +10,12 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Annotation\Route;
#[Route('/user')]
final class UserController extends AbstractController final class UserController extends AbstractController
{ {
#[Route(name: 'app_user_index', methods: ['GET'])] // Route pour afficher tous les utilisateurs
#[Route('/user', name: 'app_user_index', methods: ['GET'])]
public function index(UserRepository $userRepository): Response public function index(UserRepository $userRepository): Response
{ {
return $this->render('user/index.html.twig', [ return $this->render('user/index.html.twig', [
@ -23,7 +23,8 @@ final class UserController extends AbstractController
]); ]);
} }
#[Route('/new', name: 'app_user_new', methods: ['GET', 'POST'])] // Route pour créer un nouvel utilisateur
#[Route('/user/new', name: 'app_user_new', methods: ['GET', 'POST'])]
public function new(Request $request, EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordHasher): Response public function new(Request $request, EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordHasher): Response
{ {
$user = new Utilisateur(); $user = new Utilisateur();
@ -48,7 +49,8 @@ final class UserController extends AbstractController
]); ]);
} }
#[Route('/{id}', name: 'app_user_show', methods: ['GET'])] // Route pour afficher un utilisateur spécifique
#[Route('/user/{id}', name: 'app_user_show', methods: ['GET'])]
public function show(Utilisateur $user): Response public function show(Utilisateur $user): Response
{ {
return $this->render('user/show.html.twig', [ return $this->render('user/show.html.twig', [
@ -56,9 +58,21 @@ final class UserController extends AbstractController
]); ]);
} }
#[Route('/{id}/edit', name: 'app_user_edit', methods: ['GET', 'POST'])] // Route pour modifier un utilisateur spécifique
#[Route('/user/{id}/edit', name: 'app_user_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Utilisateur $user, EntityManagerInterface $entityManager): Response public function edit(Request $request, Utilisateur $user, EntityManagerInterface $entityManager): Response
{ {
if ($this->isGranted('ROLE_SECRETAIRE') && $user->hasRole('ROLE_SECRETAIRE')) {
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier un autre secrétaire.');
}
if ($this->isGranted('ROLE_CHAUFFAGISTE') && ($user->hasRole('ROLE_CHAUFFAGISTE') || $user->hasRole('ROLE_SECRETAIRE'))) {
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier un admin.');
}
// On s'assure que seul un admin peut éditer un autre admin
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$form = $this->createForm(UserType::class, $user); $form = $this->createForm(UserType::class, $user);
$form->handleRequest($request); $form->handleRequest($request);
@ -74,10 +88,24 @@ final class UserController extends AbstractController
]); ]);
} }
#[Route('/{id}', name: 'app_user_delete', methods: ['POST'])] // Route pour supprimer un utilisateur spécifique
#[Route('/user/{id}', name: 'app_user_delete', methods: ['POST'])]
public function delete(Request $request, Utilisateur $user, EntityManagerInterface $entityManager): Response public function delete(Request $request, Utilisateur $user, EntityManagerInterface $entityManager): Response
{ {
if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->getPayload()->getString('_token'))) { // Si l'utilisateur est un secrétaire et qu'il essaie de supprimer un autre secrétaire
if ($this->isGranted('ROLE_SECRETAIRE') && $user->hasRole('ROLE_SECRETAIRE')) {
throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer un autre secrétaire ou un administrateur.');
}
// Si l'utilisateur est un chauffagiste et qu'il essaie de supprimer un admin
if ($this->isGranted('ROLE_CHAUFFAGISTE') && ($user->hasRole('ROLE_SECRETAIRE') || $user->hasRole('ROLE_CHAUFFAGISTE'))) {
throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer un utilisateur.');
}
// On s'assure que seul un admin peut supprimer un autre admin
$this->denyAccessUnlessGranted("ROLE_ADMIN");
if ($this->isCsrfTokenValid('delete' . $user->getId(), $request->get('csrf_token'))) {
$entityManager->remove($user); $entityManager->remove($user);
$entityManager->flush(); $entityManager->flush();
} }

View File

@ -17,8 +17,13 @@ final class VehicleController extends AbstractController
#[Route(name: 'app_vehicle_index', methods: ['GET'])] #[Route(name: 'app_vehicle_index', methods: ['GET'])]
public function index(VehicleRepository $vehicleRepository): Response public function index(VehicleRepository $vehicleRepository): Response
{ {
// Admin peut voir tous les véhicules, chauffagiste ne peut voir que ses véhicules
$vehicles = $this->isGranted('ROLE_CHAUFFAGISTE')
? $vehicleRepository->findByUser($this->getUser()) // Filtre les véhicules par utilisateur
: $vehicleRepository->findAll(); // Les admins voient tout
return $this->render('vehicle/index.html.twig', [ return $this->render('vehicle/index.html.twig', [
'vehicles' => $vehicleRepository->findAll(), 'vehicles' => $vehicles,
]); ]);
} }
@ -30,6 +35,11 @@ final class VehicleController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
// Si l'utilisateur est un chauffagiste, on associe le véhicule à lui
if ($this->isGranted('ROLE_CHAUFFAGISTE')) {
$vehicle->setUser($this->getUser());
}
$entityManager->persist($vehicle); $entityManager->persist($vehicle);
$entityManager->flush(); $entityManager->flush();
@ -45,6 +55,11 @@ final class VehicleController extends AbstractController
#[Route('/{id}', name: 'app_vehicle_show', methods: ['GET'])] #[Route('/{id}', name: 'app_vehicle_show', methods: ['GET'])]
public function show(Vehicle $vehicle): Response public function show(Vehicle $vehicle): Response
{ {
// Si l'utilisateur est un chauffagiste et essaie de voir un véhicule d'un autre chauffagiste, on bloque
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $vehicle->getUser() !== $this->getUser()) {
throw $this->createAccessDeniedException('Vous ne pouvez pas voir ce véhicule.');
}
return $this->render('vehicle/show.html.twig', [ return $this->render('vehicle/show.html.twig', [
'vehicle' => $vehicle, 'vehicle' => $vehicle,
]); ]);
@ -53,6 +68,11 @@ final class VehicleController extends AbstractController
#[Route('/{id}/edit', name: 'app_vehicle_edit', methods: ['GET', 'POST'])] #[Route('/{id}/edit', name: 'app_vehicle_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Vehicle $vehicle, EntityManagerInterface $entityManager): Response public function edit(Request $request, Vehicle $vehicle, EntityManagerInterface $entityManager): Response
{ {
// Vérifier si un chauffagiste essaie de modifier un véhicule d'un autre chauffagiste
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $vehicle->getUser() !== $this->getUser()) {
throw $this->createAccessDeniedException('Vous ne pouvez pas modifier ce véhicule.');
}
$form = $this->createForm(VehicleType::class, $vehicle); $form = $this->createForm(VehicleType::class, $vehicle);
$form->handleRequest($request); $form->handleRequest($request);
@ -71,7 +91,12 @@ final class VehicleController extends AbstractController
#[Route('/{id}', name: 'app_vehicle_delete', methods: ['POST'])] #[Route('/{id}', name: 'app_vehicle_delete', methods: ['POST'])]
public function delete(Request $request, Vehicle $vehicle, EntityManagerInterface $entityManager): Response public function delete(Request $request, Vehicle $vehicle, EntityManagerInterface $entityManager): Response
{ {
if ($this->isCsrfTokenValid('delete'.$vehicle->getId(), $request->getPayload()->getString('_token'))) { // Vérification de sécurité : un chauffagiste ne peut supprimer un véhicule d'un autre chauffagiste
if ($this->isGranted('ROLE_CHAUFFAGISTE') && $vehicle->getUser() !== $this->getUser()) {
throw $this->createAccessDeniedException('Vous ne pouvez pas supprimer ce véhicule.');
}
if ($this->isCsrfTokenValid('delete'.$vehicle->getId(), $request->get('csrf_token'))) {
$entityManager->remove($vehicle); $entityManager->remove($vehicle);
$entityManager->flush(); $entityManager->flush();
} }

View File

@ -32,7 +32,6 @@ class Fault
public function setWording(string $Wording): static public function setWording(string $Wording): static
{ {
$this->Wording = $Wording; $this->Wording = $Wording;
return $this; return $this;
} }
@ -44,7 +43,6 @@ class Fault
public function setDescription(string $Description): static public function setDescription(string $Description): static
{ {
$this->Description = $Description; $this->Description = $Description;
return $this; return $this;
} }
} }

View File

@ -5,6 +5,8 @@ namespace App\Entity;
use App\Repository\InterventionRepository; use App\Repository\InterventionRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\Entity(repositoryClass: InterventionRepository::class)] #[ORM\Entity(repositoryClass: InterventionRepository::class)]
class Intervention class Intervention
@ -29,9 +31,31 @@ class Intervention
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
private ?string $Status = null; private ?string $Status = null;
<<<<<<< HEAD
// #[ORM\ManyToOne(targetEntity: Vehicule::class)] // #[ORM\ManyToOne(targetEntity: Vehicule::class)]
// private ?Vehicule $vehicule = null; // private ?Vehicule $vehicule = null;
=======
#[ORM\ManyToOne(inversedBy: 'interventions')]
#[ORM\JoinColumn(nullable: false)]
private ?Utilisateur $user = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?Fault $fault = null;
#[ORM\ManyToOne(inversedBy: 'intervention')]
private ?Vehicle $vehicle = null;
#[ORM\ManyToMany(targetEntity: Stock::class)]
#[ORM\JoinTable(name: "intervention_stock")]
private Collection $stocks;
public function __construct()
{
$this->stocks = new ArrayCollection();
}
>>>>>>> 4fc91211f0d814453d2ed97caf6a1d94d709058e
public function getId(): ?int public function getId(): ?int
{ {
@ -46,7 +70,6 @@ class Intervention
public function setWording(string $Wording): static public function setWording(string $Wording): static
{ {
$this->Wording = $Wording; $this->Wording = $Wording;
return $this; return $this;
} }
@ -58,7 +81,6 @@ class Intervention
public function setTimestamp(\DateTimeInterface $Timestamp): static public function setTimestamp(\DateTimeInterface $Timestamp): static
{ {
$this->Timestamp = $Timestamp; $this->Timestamp = $Timestamp;
return $this; return $this;
} }
@ -70,7 +92,6 @@ class Intervention
public function setDescription(string $Description): static public function setDescription(string $Description): static
{ {
$this->Description = $Description; $this->Description = $Description;
return $this; return $this;
} }
@ -82,7 +103,6 @@ class Intervention
public function setAddress(string $Address): static public function setAddress(string $Address): static
{ {
$this->Address = $Address; $this->Address = $Address;
return $this; return $this;
} }
@ -94,7 +114,58 @@ class Intervention
public function setStatus(string $Status): static public function setStatus(string $Status): static
{ {
$this->Status = $Status; $this->Status = $Status;
return $this;
}
public function getUser(): ?Utilisateur
{
return $this->user;
}
public function setUser(?Utilisateur $user): static
{
$this->user = $user;
return $this;
}
public function getFault(): ?Fault
{
return $this->fault;
}
public function setFault(?Fault $fault): static
{
$this->fault = $fault;
return $this;
}
public function getVehicle(): ?Vehicle
{
return $this->vehicle;
}
public function setVehicle(?Vehicle $vehicle): static
{
$this->vehicle = $vehicle;
return $this;
}
public function getStocks(): Collection
{
return $this->stocks;
}
public function addStock(Stock $stock): static
{
if (!$this->stocks->contains($stock)) {
$this->stocks[] = $stock;
}
return $this;
}
public function removeStock(Stock $stock): static
{
$this->stocks->removeElement($stock);
return $this; return $this;
} }
} }

View File

@ -4,6 +4,8 @@ namespace App\Entity;
use App\Repository\SkillRepository; use App\Repository\SkillRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\Entity(repositoryClass: SkillRepository::class)] #[ORM\Entity(repositoryClass: SkillRepository::class)]
class Skill class Skill
@ -19,6 +21,14 @@ class Skill
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
private ?string $Description = null; private ?string $Description = null;
#[ORM\ManyToMany(mappedBy: 'skills', targetEntity: Utilisateur::class)]
private Collection $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@ -32,7 +42,6 @@ class Skill
public function setWording(string $Wording): static public function setWording(string $Wording): static
{ {
$this->Wording = $Wording; $this->Wording = $Wording;
return $this; return $this;
} }
@ -44,7 +53,28 @@ class Skill
public function setDescription(string $Description): static public function setDescription(string $Description): static
{ {
$this->Description = $Description; $this->Description = $Description;
return $this;
}
public function getUsers(): Collection
{
return $this->users;
}
public function addUser(Utilisateur $user): static
{
if (!$this->users->contains($user)) {
$this->users[] = $user;
$user->addSkill($this);
}
return $this;
}
public function removeUser(Utilisateur $user): static
{
if ($this->users->removeElement($user)) {
$user->removeSkill($this);
}
return $this; return $this;
} }
} }

View File

@ -4,6 +4,8 @@ namespace App\Entity;
use App\Repository\StockRepository; use App\Repository\StockRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\Entity(repositoryClass: StockRepository::class)] #[ORM\Entity(repositoryClass: StockRepository::class)]
class Stock class Stock
@ -22,6 +24,14 @@ class Stock
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
private ?string $Quantity = null; private ?string $Quantity = null;
#[ORM\ManyToMany(mappedBy: 'stocks', targetEntity: Intervention::class)]
private Collection $interventions;
public function __construct()
{
$this->interventions = new ArrayCollection();
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@ -35,7 +45,6 @@ class Stock
public function setWording(string $Wording): static public function setWording(string $Wording): static
{ {
$this->Wording = $Wording; $this->Wording = $Wording;
return $this; return $this;
} }
@ -47,7 +56,6 @@ class Stock
public function setDescription(string $Description): static public function setDescription(string $Description): static
{ {
$this->Description = $Description; $this->Description = $Description;
return $this; return $this;
} }
@ -59,7 +67,28 @@ class Stock
public function setQuantity(string $Quantity): static public function setQuantity(string $Quantity): static
{ {
$this->Quantity = $Quantity; $this->Quantity = $Quantity;
return $this;
}
public function getInterventions(): Collection
{
return $this->interventions;
}
public function addIntervention(Intervention $intervention): static
{
if (!$this->interventions->contains($intervention)) {
$this->interventions[] = $intervention;
$intervention->addStock($this);
}
return $this;
}
public function removeIntervention(Intervention $intervention): static
{
if ($this->interventions->removeElement($intervention)) {
$intervention->removeStock($this);
}
return $this; return $this;
} }
} }

View File

@ -5,6 +5,8 @@ namespace App\Entity;
use App\Repository\UserRepository; use App\Repository\UserRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
@ -32,8 +34,118 @@ class Utilisateur implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
private ?string $Phone = null; private ?string $Phone = null;
#[ORM\Column]
private array $roles = [];
#[ORM\Column]
private ?string $password = null;
private ?string $plainPassword = null; private ?string $plainPassword = null;
#[ORM\OneToMany(mappedBy: 'user', targetEntity: Intervention::class)]
private Collection $interventions;
#[ORM\ManyToMany(targetEntity: Skill::class)]
#[ORM\JoinTable(name: "user_skill")]
private Collection $skills;
public function __construct()
{
$this->interventions = new ArrayCollection();
$this->skills = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
public function getFirstName(): ?string
{
return $this->FirstName;
}
public function setFirstName(?string $FirstName): void
{
$this->FirstName = $FirstName;
}
public function getLastName(): ?string
{
return $this->LastName;
}
public function setLastName(?string $LastName): void
{
$this->LastName = $LastName;
}
public function getBirthDate(): ?\DateTimeInterface
{
return $this->BirthDate;
}
public function setBirthDate(?\DateTimeInterface $BirthDate): void
{
$this->BirthDate = $BirthDate;
}
public function getPhone(): ?string
{
return $this->Phone;
}
public function setPhone(?string $Phone): void
{
$this->Phone = $Phone;
}
public function getRoles(): array
{
$roles = $this->roles;
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): static
{
$this->roles = $roles;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): static
{
$this->password = $password;
return $this;
}
public function getUserIdentifier(): string
{
return (string) $this->email;
}
public function eraseCredentials(): void
{
$this->plainPassword = null;
}
public function getPlainPassword(): ?string public function getPlainPassword(): ?string
{ {
return $this->plainPassword; return $this->plainPassword;
@ -44,154 +156,46 @@ class Utilisateur implements UserInterface, PasswordAuthenticatedUserInterface
$this->plainPassword = $plainPassword; $this->plainPassword = $plainPassword;
} }
/** public function getInterventions(): Collection
* @var list<string> The user roles
*/
#[ORM\Column]
private array $roles = [];
/**
* @var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
public function getId(): ?int
{ {
return $this->id; return $this->interventions;
} }
/** public function addIntervention(Intervention $intervention): static
* @return \DateTimeInterface|null
*/
public function getBirthDate(): ?\DateTimeInterface
{ {
return $this->BirthDate; if (!$this->interventions->contains($intervention)) {
} $this->interventions[] = $intervention;
$intervention->setUser($this);
/** }
* @param \DateTimeInterface|null $BirthDate
*/
public function setBirthDate(?\DateTimeInterface $BirthDate): void
{
$this->BirthDate = $BirthDate;
}
/**
* @return string|null
*/
public function getLastName(): ?string
{
return $this->LastName;
}
/**
* @param string|null $LastName
*/
public function setLastName(?string $LastName): void
{
$this->LastName = $LastName;
}
/**
* @return string|null
*/
public function getFirstName(): ?string
{
return $this->FirstName;
}
/**
* @param string|null $FirstName
*/
public function setFirstName(?string $FirstName): void
{
$this->FirstName = $FirstName;
}
/**
* @return string|null
*/
public function getPhone(): ?string
{
return $this->Phone;
}
/**
* @param string|null $Phone
*/
public function setPhone(?string $Phone): void
{
$this->Phone = $Phone;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this; return $this;
} }
/** public function removeIntervention(Intervention $intervention): static
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{ {
return (string) $this->email; if ($this->interventions->removeElement($intervention)) {
} if ($intervention->getUser() === $this) {
$intervention->setUser(null);
/** }
* @see UserInterface }
*
* @return list<string>
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
/**
* @param list<string> $roles
*/
public function setRoles(array $roles): static
{
$this->roles = $roles;
return $this; return $this;
} }
/** public function getSkills(): Collection
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): ?string
{ {
return $this->password; return $this->skills;
} }
public function setPassword(string $password): static public function addSkill(Skill $skill): static
{ {
$this->password = $password; if (!$this->skills->contains($skill)) {
$this->skills[] = $skill;
}
return $this; return $this;
} }
/** public function removeSkill(Skill $skill): static
* @see UserInterface
*/
public function eraseCredentials(): void
{ {
// If you store any temporary, sensitive data on the user, clear it here $this->skills->removeElement($skill);
$this->plainPassword = null; return $this;
} }
} }

View File

@ -4,6 +4,8 @@ namespace App\Entity;
use App\Repository\VehicleRepository; use App\Repository\VehicleRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\Entity(repositoryClass: VehicleRepository::class)] #[ORM\Entity(repositoryClass: VehicleRepository::class)]
class Vehicle class Vehicle
@ -22,6 +24,14 @@ class Vehicle
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
private ?string $Model = null; private ?string $Model = null;
#[ORM\OneToMany(mappedBy: 'vehicle', targetEntity: Intervention::class)]
private Collection $intervention;
public function __construct()
{
$this->intervention = new ArrayCollection();
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@ -35,7 +45,6 @@ class Vehicle
public function setLicensePlate(string $LicensePlate): static public function setLicensePlate(string $LicensePlate): static
{ {
$this->LicensePlate = $LicensePlate; $this->LicensePlate = $LicensePlate;
return $this; return $this;
} }
@ -47,7 +56,6 @@ class Vehicle
public function setBrand(string $Brand): static public function setBrand(string $Brand): static
{ {
$this->Brand = $Brand; $this->Brand = $Brand;
return $this; return $this;
} }
@ -60,4 +68,28 @@ class Vehicle
{ {
$this->Model = $Model; $this->Model = $Model;
} }
public function getIntervention(): Collection
{
return $this->intervention;
}
public function addIntervention(Intervention $intervention): static
{
if (!$this->intervention->contains($intervention)) {
$this->intervention[] = $intervention;
$intervention->setVehicle($this);
}
return $this;
}
public function removeIntervention(Intervention $intervention): static
{
if ($this->intervention->removeElement($intervention)) {
if ($intervention->getVehicle() === $this) {
$intervention->setVehicle(null);
}
}
return $this;
}
} }

View File

@ -3,6 +3,7 @@
namespace App\Form; namespace App\Form;
use App\Entity\Fault; use App\Entity\Fault;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -14,6 +15,7 @@ class FaultType extends AbstractType
{ {
$builder $builder
->add('Wording', TextType::class) ->add('Wording', TextType::class)
->add('Description', TextType::class)
; ;
} }

View File

@ -2,9 +2,14 @@
namespace App\Form; namespace App\Form;
use App\Entity\Fault;
use App\Entity\Intervention; use App\Entity\Intervention;
use App\Entity\Stock;
use App\Entity\Utilisateur;
use App\Entity\Vehicle;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@ -15,12 +20,31 @@ class InterventionType extends AbstractType
{ {
$builder $builder
->add('Wording', TextType::class) ->add('Wording', TextType::class)
->add('Timestamp', DateType::class, [ ->add('Timestamp', DateTimeType::class, [
'widget' => 'single_text', 'widget' => 'single_text',
]) ])
->add('Description', TextType::class) ->add('Description', TextType::class)
->add('Address', TextType::class) ->add('Address', TextType::class)
->add('Status', TextType::class) ->add('Status', TextType::class)
->add('user', EntityType::class, [
'class' => Utilisateur::class,
'choice_label' => 'FirstName', // ou autre (LastName, email)
])
->add('fault', EntityType::class, [
'class' => Fault::class,
'choice_label' => 'Wording',
])
->add('vehicle', EntityType::class, [
'class' => Vehicle::class,
'choice_label' => 'LicensePlate',
'required' => false,
])
->add('stocks', EntityType::class, [
'class' => Stock::class,
'choice_label' => 'Wording',
'multiple' => true,
'expanded' => true,
])
; ;
// // 👉 Sélecteur de véhicule // // 👉 Sélecteur de véhicule

View File

@ -13,7 +13,7 @@ class SkillType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
$builder $builder
->add('Wording',TextType::class) ->add('Wording', TextType::class)
->add('Description', TextType::class) ->add('Description', TextType::class)
; ;
} }

View File

@ -2,7 +2,9 @@
namespace App\Form; namespace App\Form;
use App\Entity\Skill;
use App\Entity\Utilisateur; use App\Entity\Utilisateur;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\DateType;
@ -27,14 +29,24 @@ class UserType extends AbstractType
'choices' => [ 'choices' => [
'Secretaire' => 'ROLE_SECRETAIRE', 'Secretaire' => 'ROLE_SECRETAIRE',
'Chauffagiste' => 'ROLE_CHAUFFAGISTE', 'Chauffagiste' => 'ROLE_CHAUFFAGISTE',
'Admin' => 'ROLE_ADMIN', // Ajouté si besoin de créer des admins
], ],
'multiple' => true, 'multiple' => true,
'expanded' => true, // pour avoir des cases à cocher
])
->add('skills', EntityType::class, [
'class' => Skill::class,
'choice_label' => 'Wording',
'multiple' => true,
'expanded' => true,
'required' => false,
]) ])
->add('plainPassword', PasswordType::class, [ ->add('plainPassword', PasswordType::class, [
'mapped' => false, 'mapped' => false,
'required' => true, 'required' => true,
'label' => 'Mot de passe', 'label' => 'Mot de passe',
]); ])
;
} }
public function configureOptions(OptionsResolver $resolver): void public function configureOptions(OptionsResolver $resolver): void

View File

@ -3,45 +3,119 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{% block title %}Mon App Symfony{% endblock %}</title> <title>{% block title %}Mon App Symfony{% endblock %}</title>
<style>
body {
font-family: sans-serif;
margin: 0;
padding: 0;
background-color: #f9f9f9;
}
header {
background-color: #222;
color: white;
padding: 1rem;
}
.user-info {
display: flex;
justify-content: space-between;
align-items: center;
}
nav {
background-color: #eee;
padding: 1rem;
}
nav ul {
list-style: none;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin: 0;
}
nav li a {
text-decoration: none;
color: #0077cc;
font-weight: bold;
}
nav li a:hover {
text-decoration: underline;
}
.content {
padding: 2rem;
}
.role-badge {
background: #ddd;
padding: 0.2rem 0.6rem;
border-radius: 4px;
margin-right: 0.3rem;
font-size: 0.85em;
}
.logout-btn {
background: transparent;
color: white;
border: 1px solid white;
padding: 0.3rem 0.8rem;
text-decoration: none;
border-radius: 4px;
}
.logout-btn:hover {
background-color: white;
color: #222;
}
</style>
{% block stylesheets %}{% endblock %} {% block stylesheets %}{% endblock %}
<!-- FullCalendar pour faire un calendrier dynamique -->
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.js"></script>
</head> </head>
<body> <body>
{% if app.user %} {% if app.user %}
<div class="user-info"> <header>
Connecté en tant que {{ app.user.email }} | <div class="user-info">
Rôles : <div>
{% for role in app.user.roles %} <strong>Connecté en tant que</strong> {{ app.user.email }}<br>
{{ role }}{% if not loop.last %}, {% endif %} </div>
{% endfor %} <a href="{{ path('app_logout') }}" class="logout-btn">Déconnexion</a>
</div> </div>
</header>
<nav> <nav>
<ul> <ul>
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_ADMIN') %}
<li><a href="{{ path('admin_dashboard') }}">Dashboard Admin</a></li> <li><a href="{{ path('admin_dashboard') }}">Dashboard Admin</a></li>
<li><a href="{{ path('app_user_index') }}">Gérer un utilisateur</a></li> <li><a href="{{ path('app_user_index') }}">Gérer les utilisateurs</a></li>
<li><a href="{{ path('app_calendrier_index') }}">Tous les plannings</a></li>
<li><a href="{{ path('app_stock_index') }}">Gérer les stocks</a></li>
<li><a href="{{ path('app_vehicle_index') }}">Gérer les véhicules</a></li> <li><a href="{{ path('app_vehicle_index') }}">Gérer les véhicules</a></li>
<li><a href="{{ path('app_fault_index') }}">Test des fault !</a></li> <li><a href="{{ path('app_stock_index') }}">Gérer les stocks</a></li>
<li><a href="{{ path('app_fault_index') }}">Gérer les pannes</a></li>
<li><a href="{{ path('app_calendrier_index') }}">Tous les plannings</a></li>
{% endif %} {% endif %}
{% if is_granted('ROLE_SECRETAIRE') %} {% if is_granted('ROLE_SECRETAIRE') %}
<li><a href="{{ path('secretaire_dashboard') }}">Dashboard Secrétaire</a></li> <li><a href="{{ path('secretaire_dashboard') }}">Dashboard Secrétaire</a></li>
<li><a href="{{ path('user_new') }}">Créer un chauffagiste</a></li> <li><a href="{{ path('app_user_index') }}">Créer un chauffagiste</a></li>
<li><a href="{{ path('calendrier_index_secretaire') }}">Plannings chauffagistes</a></li> <li><a href="{{ path('app_vehicle_index') }}">Gérer les véhicules</a></li>
<li><a href="{{ path('stock_index') }}">Gérer les stocks</a></li> <li><a href="{{ path('app_stock_index') }}">Gérer les stocks</a></li>
<li><a href="{{ path('vehicle_index') }}">Gérer les véhicules</a></li> <li><a href="{{ path('app_calendrier_indexSecretaire') }}">Plannings chauffagistes</a></li>
{% endif %} {% endif %}
{% if is_granted('ROLE_CHAUFFAGISTE') %} {% if is_granted('ROLE_CHAUFFAGISTE') %}
<li><a href="{{ path('chauffagiste_dashboard') }}">Dashboard Chauffagiste</a></li> <li><a href="{{ path('chauffagiste_dashboard') }}">Dashboard Chauffagiste</a></li>
<li><a href="{{ path('calendrier_index_chauffagiste') }}">Mon planning</a></li> <li><a href="{{ path('app_intervention_index') }}">Mes interventions</a></li>
<li><a href="{{ path('intervention_index') }}">Mes interventions</a></li> <li><a href="{{ path('app_stock_index') }}">Pièces détachées</a></li>
<li><a href="{{ path('stock_index') }}">Pièces détachées</a></li> <li><a href="{{ path('app_calendrier_indexChauffagiste') }}">Mon planning</a></li>
{% endif %} {% endif %}
<li><a href="{{ path('app_logout') }}">Déconnexion</a></li>
</ul> </ul>
</nav> </nav>
{% endif %} {% endif %}

View File

@ -1,57 +1,23 @@
<!DOCTYPE html> {% extends 'base.html.twig' %}
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Authentification{% endblock %}</title>
<link href="{{ asset('styles/css/calendrier.css') }}" rel="stylesheet">
{% block stylesheets %}
{% endblock %}
</head>
<body> {% block title %}Calendrier des interventions{% endblock %}
<div class="sidebar">
<div class="sidebar-button">
<img class="logoEmail" src="{{ asset('styles/image/6.png') }}">
</div>
<div class="sidebar-button">
<img class="logoEmail" src="{{ asset('styles/image/3.png') }}">
</div>
<a href="{{ path('app_intervention_new') }}">
<button>Create Intervention</button>
</a>
<div class="sidebar-button">
<img class="logoEmail" src="{{ asset('styles/image/4.png') }}">
</div>
<span>Choose heating engineer</span>
<div class="sidebar-button">
<img class="logoEmail" src="{{ asset('styles/image/5.png') }}">
</div>
<span>Modify Intervention</span>
<div class="sidebar-button">
<img class="logoEmail" src="{{ asset('styles/image/7.png') }}">
</div>
<span>Surname Name</span>
<div class="sidebar-button"> {% block body %}
<img class="logoEmail" src="{{ asset('styles/image/LogoHegreConfort_SansFond.png') }}"> <h1>📅 Calendrier des interventions</h1>
</div>
<div id="calendar"></div>
</div> <script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
<div class="main-content"> var calendar = new FullCalendar.Calendar(calendarEl, {
<div class="calendar-header" style="gap: 50px"> initialView: 'dayGridMonth',
<div class="day-header">Mo</div> events: {{ events | raw }},
<div class="day-header">Tu</div> eventClick: function(info) {
<div class="day-header">We</div> alert('Intervention : ' + info.event.title + '\n' + info.event.extendedProps.description);
<div class="day-header">Th</div> }
<div class="day-header">Fr</div> });
<div class="day-header">Sa</div> calendar.render();
<div class="day-header">Su</div> });
</div> </script>
<div class="calendar-title">Calendar</div> {% endblock %}
</div>
</body>
</html>

View File

@ -0,0 +1,23 @@
{% extends 'base.html.twig' %}
{% block title %}Mon calendrier d'interventions{% endblock %}
{% block body %}
<h1>📅 Mon calendrier d'interventions</h1>
<div id="calendar"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
events: {{ events | raw }},
eventClick: function(info) {
alert('Intervention : ' + info.event.title + '\n' + info.event.extendedProps.description);
}
});
calendar.render();
});
</script>
{% endblock %}

View File

@ -1,20 +1,23 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Hello CalendrierController!{% endblock %} {% block title %}Calendrier des interventions{% endblock %}
{% block body %} {% block body %}
<style> <h1>📅 Calendrier des interventions</h1>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>
<div class="example-wrapper"> <div id="calendar"></div>
<h1>Hello {{ controller_name }}! ✅</h1>
This friendly message is coming from: <script>
<ul> document.addEventListener('DOMContentLoaded', function() {
<li>Your controller at <code>/home/allarda@stsio.lan/Test/HegreEtConfort/src/Controller/CalendrierController.php</code></li> var calendarEl = document.getElementById('calendar');
<li>Your template at <code>/home/allarda@stsio.lan/Test/HegreEtConfort/templates/calendrier/index.html.twig</code></li> var calendar = new FullCalendar.Calendar(calendarEl, {
</ul> initialView: 'dayGridMonth',
</div> events: {{ events | raw }},
eventClick: function(info) {
alert('Intervention : ' + info.event.title + '\n' + info.event.extendedProps.description);
}
});
calendar.render();
});
</script>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,15 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block body %} {% block title %}Dashboard Administrateur{% endblock %}
<h1>Bienvenue Admin</h1>
<p>Tu as tous les droits ici !</p>
{% endblock %}
{% block body %}
<h1 class="mb-4">👑 Bienvenue {{ app.user.FirstName }} sur le Dashboard Administrateur</h1>
<p>Vous êtes connecté avec les rôles :
{% for role in app.user.roles %}
<span class="badge bg-primary">{{ role }}</span>
{% endfor %}
</p>
<p class="text-muted">Utilisez le menu en haut pour accéder aux fonctionnalités.</p>
{% endblock %}

View File

@ -1,7 +0,0 @@
{% extends 'base.html.twig' %}
{% block body %}
<h1>Bienvenue chauffagiste</h1>
<p>Vas travailler</p>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'base.html.twig' %}
{% block title %}Dashboard Chauffagiste{% endblock %}
{% block body %}
<h1 class="mb-4">🔧 Bonjour {{ app.user.FirstName }}, bienvenue sur ton espace</h1>
<p>Rôle :
{% for role in app.user.roles %}
<span class="badge bg-success">{{ role }}</span>
{% endfor %}
</p>
<p class="text-muted">Retrouvez votre planning, vos interventions et vos outils dans le menu ci-dessus.</p>
{% endblock %}

View File

@ -1,7 +1,15 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block body %} {% block title %}Dashboard Secrétaire{% endblock %}
<h1>Bienvenue Secretaire</h1>
<p>Fais ce que tu veux</p>
{% endblock %}
{% block body %}
<h1 class="mb-4">🗂️ Bienvenue {{ app.user.FirstName }} sur le Dashboard Secrétaire</h1>
<p>Vous êtes connectée avec les rôles :
{% for role in app.user.roles %}
<span class="badge bg-secondary">{{ role }}</span>
{% endfor %}
</p>
<p class="text-muted">Utilisez le menu pour créer des interventions, gérer les chauffagistes, etc.</p>
{% endblock %}

View File

@ -1,13 +1,14 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Edit Fault{% endblock %} {% block title %}Modifier la panne{% endblock %}
{% block body %} {% block body %}
<h1>Edit Fault</h1> <h1 class="mb-4">✏️ Modifier une panne</h1>
{{ include('fault/_form.html.twig', {'button_label': 'Update'}) }} {{ include('fault/_form.html.twig', {'button_label': 'Mettre à jour'}) }}
<a href="{{ path('app_fault_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_fault_index') }}" class="btn btn-secondary">← Retour à la liste</a>
{{ include('fault/_delete_form.html.twig') }} {{ include('fault/_delete_form.html.twig') }}
</div>
{% endblock %} {% endblock %}

View File

@ -1,36 +1,37 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Fault index{% endblock %} {% block title %}Liste des pannes{% endblock %}
{% block body %} {% block body %}
<h1>Fault index</h1> <h1 class="mb-4">📋 Liste des pannes enregistrées</h1>
<table class="table"> <table class="table table-striped table-bordered align-middle">
<thead> <thead class="table-dark">
<tr> <tr>
<th>Id</th> <th>ID</th>
<th>Wording</th> <th>Nom</th>
<th>actions</th> <th>Description</th>
</tr> <th class="text-center">Actions</th>
</tr>
</thead> </thead>
<tbody> <tbody>
{% for fault in faults %} {% for fault in faults %}
<tr> <tr>
<td>{{ fault.id }}</td> <td>{{ fault.id }}</td>
<td>{{ fault.Wording }}</td> <td>{{ fault.Wording }}</td>
<td>{{ fault.actions }}</td> <td>{{ fault.Description }}</td>
<td> <td class="text-center">
<a href="{{ path('app_fault_show', {'id': fault.id}) }}">show</a> <a href="{{ path('app_fault_show', {'id': fault.id}) }}" class="btn btn-sm btn-info">👁️ Voir</a>
<a href="{{ path('app_fault_edit', {'id': fault.id}) }}">edit</a> <a href="{{ path('app_fault_edit', {'id': fault.id}) }}" class="btn btn-sm btn-warning">✏️ Modifier</a>
</td> </td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="3">no records found</td> <td colspan="4" class="text-center text-muted">Aucune panne trouvée.</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_fault_new') }}">Create new</a> <a href="{{ path('app_fault_new') }}" class="btn btn-success mt-3"> Ajouter une nouvelle panne</a>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,13 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}New Fault{% endblock %} {% block title %}Créer une panne{% endblock %}
{% block body %} {% block body %}
<h1>Create new Fault</h1> <h1 class="mb-4"> Ajouter une nouvelle panne</h1>
{{ include('fault/_form.html.twig') }} {{ include('fault/_form.html.twig') }}
<a href="{{ path('app_fault_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_fault_index') }}" class="btn btn-secondary">← Retour à la liste</a>
</div>
{% endblock %} {% endblock %}

View File

@ -1,26 +1,30 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Fault{% endblock %} {% block title %}Détail de la panne{% endblock %}
{% block body %} {% block body %}
<h1>Fault</h1> <h1 class="mb-4">🔍 Détails de la panne</h1>
<table class="table"> <table class="table table-bordered">
<tbody> <tbody>
<tr> <tr>
<th>Id</th> <th>ID</th>
<td>{{ fault.id }}</td> <td>{{ fault.id }}</td>
</tr> </tr>
<tr> <tr>
<th>Wording</th> <th>Nom</th>
<td>{{ fault.Wording }}</td> <td>{{ fault.Wording }}</td>
</tr> </tr>
<tr>
<th>Description</th>
<td>{{ fault.Description }}</td>
</tr>
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_fault_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_fault_index') }}" class="btn btn-secondary">← Retour à la liste</a>
<a href="{{ path('app_fault_edit', {'id': fault.id}) }}">edit</a> <a href="{{ path('app_fault_edit', {'id': fault.id}) }}" class="btn btn-warning">✏️ Modifier</a>
{{ include('fault/_delete_form.html.twig') }}
{{ include('fault/_delete_form.html.twig') }} </div>
{% endblock %} {% endblock %}

View File

@ -1,13 +1,14 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Edit Intervention{% endblock %} {% block title %}Modifier une intervention{% endblock %}
{% block body %} {% block body %}
<h1>Edit Intervention</h1> <h1 class="mb-4">✏️ Modifier l'intervention</h1>
{{ include('intervention/_form.html.twig', {'button_label': 'Update'}) }} {{ include('intervention/_form.html.twig', {'button_label': 'Mettre à jour'}) }}
<a href="{{ path('app_intervention_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_intervention_index') }}" class="btn btn-secondary">← Retour à la liste des interventions</a>
{{ include('intervention/_delete_form.html.twig') }} {{ include('intervention/_delete_form.html.twig') }}
</div>
{% endblock %} {% endblock %}

View File

@ -1,43 +1,53 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Intervention index{% endblock %} {% block title %}Liste des interventions{% endblock %}
{% block body %} {% block body %}
<h1>Intervention index</h1> <h1>Liste des interventions</h1>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>Id</th> <th>Id</th>
<th>Wording</th> <th>Nom</th>
<th>Timestamp</th> <th>Chauffagiste</th>
<th>Description</th> <th>Véhicule</th>
<th>Address</th> <th>Panne</th>
<th>Status</th> <th>Date & Heure</th>
<th>actions</th> <th>Statut</th>
</tr> <th>Actions</th>
</tr>
</thead> </thead>
<tbody> <tbody>
{% for intervention in interventions %} {% for intervention in interventions %}
<tr> <tr>
<td>{{ intervention.id }}</td> <td>{{ intervention.id }}</td>
<td>{{ intervention.Wording }}</td> <td>{{ intervention.Wording }}</td>
<td>{{ intervention.Timestamp ? intervention.Timestamp|date('Y-m-d H:i:s') : '' }}</td> <td>
<td>{{ intervention.Description }}</td> {{ intervention.user ? intervention.user.FirstName ~ ' ' ~ intervention.user.LastName : 'Non assigné' }}
<td>{{ intervention.Address }}</td> </td>
<td>
{{ intervention.vehicle ? intervention.vehicle.LicensePlate : 'Aucun véhicule' }}
</td>
<td>
{{ intervention.fault ? intervention.fault.Wording : 'Non précisé' }}
</td>
<td>
{{ intervention.Timestamp ? intervention.Timestamp|date('Y-m-d H:i') : '' }}
</td>
<td>{{ intervention.Status }}</td> <td>{{ intervention.Status }}</td>
<td> <td>
<a href="{{ path('app_intervention_show', {'id': intervention.id}) }}">show</a> <a href="{{ path('app_intervention_show', {'id': intervention.id}) }}" class="btn btn-info btn-sm">Voir</a>
<a href="{{ path('app_intervention_edit', {'id': intervention.id}) }}">edit</a> <a href="{{ path('app_intervention_edit', {'id': intervention.id}) }}" class="btn btn-secondary btn-sm">Modifier</a>
</td> </td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="7">no records found</td> <td colspan="8">Aucune intervention trouvée</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_intervention_new') }}">Create new</a> <a href="{{ path('app_intervention_new') }}" class="btn btn-success">Créer une nouvelle intervention</a>
{% endblock %} {% endblock %}

View File

@ -1,28 +1,13 @@
<!DOCTYPE html> {% extends 'base.html.twig' %}
<html lang="fr, en">
<head>
<meta charset="UTF-8">
<title>Créer une intervention</title>
<link href="{{ asset('styles/css/intervention.css') }}" rel='stylesheet'>
<link rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<meta name="viewport" content="width=device-width, initial-scale=1"> {% block title %}Nouvelle intervention{% endblock %}
</head> {% block body %}
<body> <h1 class="mb-4"> Créer une nouvelle intervention</h1>
<div class="background"></div>
<img class="logoCalendar" src="{{ asset('styles/image/calendar.png') }}" alt="Logo Calendrier">
<a href="{{ path('app_intervention_index') }}" class="intervention">Voir les interventions</a>
<img class="logoChauffagiste" src="{{ asset('styles/image/chauffagiste.png') }}" alt="Logo Chauffagiste">
<h1 class="chauffagiste">Choisissez un chauffagiste</h1>
<img class="logoModifier" src="{{ asset('styles/image/modifier.png') }}">
<h1 class="modifier">Modifier l'intervention</h1>
<img class="logoHegre" src="{{ asset('styles/image/LogoHegreConfort_SansFond.png') }}">
{{ include('intervention/_form.html.twig') }}
<<<<<<< HEAD
<div class="background-intervention"> <div class="background-intervention">
{# <input required id="nom" placeholder="Nom de l'intervention">#} {# <input required id="nom" placeholder="Nom de l'intervention">#}
@ -79,3 +64,9 @@
</div> </div>
</body> </body>
</html> </html>
=======
<div class="mt-3">
<a href="{{ path('app_intervention_index') }}" class="btn btn-secondary">← Retour à la liste des interventions</a>
</div>
{% endblock %}
>>>>>>> 4fc91211f0d814453d2ed97caf6a1d94d709058e

View File

@ -3,40 +3,71 @@
{% block title %}Intervention{% endblock %} {% block title %}Intervention{% endblock %}
{% block body %} {% block body %}
<h1>Intervention</h1> <h1>Détails de l'Intervention</h1>
<table class="table"> <table class="table">
<tbody> <tbody>
<tr> <tr>
<th>Id</th> <th>Id</th>
<td>{{ intervention.id }}</td> <td>{{ intervention.id }}</td>
</tr> </tr>
<tr> <tr>
<th>Wording</th> <th>Nom de l'intervention</th>
<td>{{ intervention.Wording }}</td> <td>{{ intervention.Wording }}</td>
</tr> </tr>
<tr> <tr>
<th>Timestamp</th> <th>Date & Heure</th>
<td>{{ intervention.Timestamp ? intervention.Timestamp|date('Y-m-d H:i:s') : '' }}</td> <td>{{ intervention.Timestamp ? intervention.Timestamp|date('Y-m-d H:i') : '' }}</td>
</tr> </tr>
<tr> <tr>
<th>Description</th> <th>Description</th>
<td>{{ intervention.Description }}</td> <td>{{ intervention.Description }}</td>
</tr> </tr>
<tr> <tr>
<th>Address</th> <th>Adresse</th>
<td>{{ intervention.Address }}</td> <td>{{ intervention.Address }}</td>
</tr> </tr>
<tr> <tr>
<th>Status</th> <th>Statut</th>
<td>{{ intervention.Status }}</td> <td>{{ intervention.Status }}</td>
</tr> </tr>
<tr>
<th>Chauffagiste</th>
<td>
{{ intervention.user ? intervention.user.FirstName ~ ' ' ~ intervention.user.LastName : 'Non assigné' }}
</td>
</tr>
<tr>
<th>Panne</th>
<td>
{{ intervention.fault ? intervention.fault.Wording : 'Non précisé' }}
</td>
</tr>
<tr>
<th>Véhicule</th>
<td>
{{ intervention.vehicle ? intervention.vehicle.LicensePlate : 'Aucun véhicule associé' }}
</td>
</tr>
<tr>
<th>Pièces détachées utilisées</th>
<td>
{% if intervention.stocks|length > 0 %}
<ul>
{% for stock in intervention.stocks %}
<li>{{ stock.Wording }} (Quantité: {{ stock.Quantity }})</li>
{% endfor %}
</ul>
{% else %}
Aucune pièce utilisée
{% endif %}
</td>
</tr>
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_intervention_index') }}">back to list</a> <a href="{{ path('app_intervention_index') }}" class="btn btn-primary">Retour à la liste</a>
<a href="{{ path('app_intervention_edit', {'id': intervention.id}) }}" class="btn btn-warning">Modifier</a>
<a href="{{ path('app_intervention_edit', {'id': intervention.id}) }}">edit</a>
{{ include('intervention/_delete_form.html.twig') }} {{ include('intervention/_delete_form.html.twig') }}
{% endblock %} {% endblock %}

View File

@ -1,13 +1,14 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Edit Skill{% endblock %} {% block title %}Modifier une compétence{% endblock %}
{% block body %} {% block body %}
<h1>Edit Skill</h1> <h1 class="mb-4">✏️ Modifier une compétence</h1>
{{ include('skill/_form.html.twig', {'button_label': 'Update'}) }} {{ include('skill/_form.html.twig', {'button_label': 'Mettre à jour'}) }}
<a href="{{ path('app_skill_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_skill_index') }}" class="btn btn-secondary">← Retour à la liste</a>
{{ include('skill/_delete_form.html.twig') }} {{ include('skill/_delete_form.html.twig') }}
</div>
{% endblock %} {% endblock %}

View File

@ -1,18 +1,18 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Skill index{% endblock %} {% block title %}Compétences disponibles{% endblock %}
{% block body %} {% block body %}
<h1>Skill index</h1> <h1 class="mb-4">📘 Liste des compétences</h1>
<table class="table"> <table class="table table-striped table-bordered align-middle">
<thead> <thead class="table-dark">
<tr> <tr>
<th>Id</th> <th>ID</th>
<th>Wording</th> <th>Nom</th>
<th>Description</th> <th>Description</th>
<th>actions</th> <th class="text-center">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for skill in skills %} {% for skill in skills %}
@ -20,18 +20,18 @@
<td>{{ skill.id }}</td> <td>{{ skill.id }}</td>
<td>{{ skill.Wording }}</td> <td>{{ skill.Wording }}</td>
<td>{{ skill.Description }}</td> <td>{{ skill.Description }}</td>
<td> <td class="text-center">
<a href="{{ path('app_skill_show', {'id': skill.id}) }}">show</a> <a href="{{ path('app_skill_show', {'id': skill.id}) }}" class="btn btn-info btn-sm">👁️ Voir</a>
<a href="{{ path('app_skill_edit', {'id': skill.id}) }}">edit</a> <a href="{{ path('app_skill_edit', {'id': skill.id}) }}" class="btn btn-warning btn-sm">✏️ Modifier</a>
</td> </td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="4">no records found</td> <td colspan="4" class="text-center text-muted">Aucune compétence enregistrée.</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_skill_new') }}">Create new</a> <a href="{{ path('app_skill_new') }}" class="btn btn-success mt-3"> Ajouter une compétence</a>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,13 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}New Skill{% endblock %} {% block title %}Nouvelle compétence{% endblock %}
{% block body %} {% block body %}
<h1>Create new Skill</h1> <h1 class="mb-4"> Ajouter une nouvelle compétence</h1>
{{ include('skill/_form.html.twig') }} {{ include('skill/_form.html.twig') }}
<a href="{{ path('app_skill_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_skill_index') }}" class="btn btn-secondary">← Retour à la liste</a>
</div>
{% endblock %} {% endblock %}

View File

@ -1,30 +1,30 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Skill{% endblock %} {% block title %}Détail de la compétence{% endblock %}
{% block body %} {% block body %}
<h1>Skill</h1> <h1 class="mb-4">🔍 Détail de la compétence</h1>
<table class="table"> <table class="table table-bordered">
<tbody> <tbody>
<tr> <tr>
<th>Id</th> <th>ID</th>
<td>{{ skill.id }}</td> <td>{{ skill.id }}</td>
</tr> </tr>
<tr> <tr>
<th>Wording</th> <th>Nom</th>
<td>{{ skill.Wording }}</td> <td>{{ skill.Wording }}</td>
</tr> </tr>
<tr> <tr>
<th>Description</th> <th>Description</th>
<td>{{ skill.Description }}</td> <td>{{ skill.Description }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_skill_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_skill_index') }}" class="btn btn-secondary">← Retour à la liste</a>
<a href="{{ path('app_skill_edit', {'id': skill.id}) }}">edit</a> <a href="{{ path('app_skill_edit', {'id': skill.id}) }}" class="btn btn-warning">✏️ Modifier</a>
{{ include('skill/_delete_form.html.twig') }}
{{ include('skill/_delete_form.html.twig') }} </div>
{% endblock %} {% endblock %}

View File

@ -1,13 +1,14 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Edit Stock{% endblock %} {% block title %}Modifier une pièce{% endblock %}
{% block body %} {% block body %}
<h1>Edit Stock</h1> <h1 class="mb-4">✏️ Modifier une pièce détachée</h1>
{{ include('stock/_form.html.twig', {'button_label': 'Update'}) }} {{ include('stock/_form.html.twig', {'button_label': 'Mettre à jour'}) }}
<a href="{{ path('app_stock_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_stock_index') }}" class="btn btn-secondary">← Retour à la liste</a>
{{ include('stock/_delete_form.html.twig') }} {{ include('stock/_delete_form.html.twig') }}
</div>
{% endblock %} {% endblock %}

View File

@ -1,19 +1,19 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Stock index{% endblock %} {% block title %}Liste des pièces détachées{% endblock %}
{% block body %} {% block body %}
<h1>Stock index</h1> <h1 class="mb-4">📦 Pièces détachées disponibles</h1>
<table class="table"> <table class="table table-striped table-bordered align-middle">
<thead> <thead class="table-dark">
<tr> <tr>
<th>Id</th> <th>ID</th>
<th>Wording</th> <th>Nom</th>
<th>Description</th> <th>Description</th>
<th>Quantity</th> <th>Quantité</th>
<th>actions</th> <th class="text-center">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for stock in stocks %} {% for stock in stocks %}
@ -22,18 +22,18 @@
<td>{{ stock.Wording }}</td> <td>{{ stock.Wording }}</td>
<td>{{ stock.Description }}</td> <td>{{ stock.Description }}</td>
<td>{{ stock.Quantity }}</td> <td>{{ stock.Quantity }}</td>
<td> <td class="text-center">
<a href="{{ path('app_stock_show', {'id': stock.id}) }}">show</a> <a href="{{ path('app_stock_show', {'id': stock.id}) }}" class="btn btn-info btn-sm">👁️ Voir</a>
<a href="{{ path('app_stock_edit', {'id': stock.id}) }}">edit</a> <a href="{{ path('app_stock_edit', {'id': stock.id}) }}" class="btn btn-warning btn-sm">✏️ Modifier</a>
</td> </td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="5">no records found</td> <td colspan="5" class="text-center text-muted">Aucune pièce en stock.</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_stock_new') }}">Create new</a> <a href="{{ path('app_stock_new') }}" class="btn btn-success mt-3"> Ajouter une pièce</a>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,13 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}New Stock{% endblock %} {% block title %}Nouvelle pièce détachée{% endblock %}
{% block body %} {% block body %}
<h1>Create new Stock</h1> <h1 class="mb-4"> Ajouter une nouvelle pièce</h1>
{{ include('stock/_form.html.twig') }} {{ include('stock/_form.html.twig') }}
<a href="{{ path('app_stock_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_stock_index') }}" class="btn btn-secondary">← Retour à la liste</a>
</div>
{% endblock %} {% endblock %}

View File

@ -1,34 +1,34 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Stock{% endblock %} {% block title %}Détail de la pièce{% endblock %}
{% block body %} {% block body %}
<h1>Stock</h1> <h1 class="mb-4">🔍 Détail d'une pièce détachée</h1>
<table class="table"> <table class="table table-bordered">
<tbody> <tbody>
<tr> <tr>
<th>Id</th> <th>ID</th>
<td>{{ stock.id }}</td> <td>{{ stock.id }}</td>
</tr> </tr>
<tr> <tr>
<th>Wording</th> <th>Nom</th>
<td>{{ stock.Wording }}</td> <td>{{ stock.Wording }}</td>
</tr> </tr>
<tr> <tr>
<th>Description</th> <th>Description</th>
<td>{{ stock.Description }}</td> <td>{{ stock.Description }}</td>
</tr> </tr>
<tr> <tr>
<th>Quantity</th> <th>Quantité</th>
<td>{{ stock.Quantity }}</td> <td>{{ stock.Quantity }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_stock_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_stock_index') }}" class="btn btn-secondary">← Retour à la liste</a>
<a href="{{ path('app_stock_edit', {'id': stock.id}) }}">edit</a> <a href="{{ path('app_stock_edit', {'id': stock.id}) }}" class="btn btn-warning">✏️ Modifier</a>
{{ include('stock/_delete_form.html.twig') }}
{{ include('stock/_delete_form.html.twig') }} </div>
{% endblock %} {% endblock %}

View File

@ -1,13 +1,14 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Edit User{% endblock %} {% block title %}Modifier un utilisateur{% endblock %}
{% block body %} {% block body %}
<h1>Edit User</h1> <h1 class="mb-4">✏️ Modifier un utilisateur</h1>
{{ include('user/_form.html.twig', {'button_label': 'Update'}) }} {{ include('user/_form.html.twig', {'button_label': 'Mettre à jour'}) }}
<a href="{{ path('app_user_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_user_index') }}" class="btn btn-secondary">← Retour à la liste des utilisateurs</a>
{{ include('user/_delete_form.html.twig') }} {{ include('user/_delete_form.html.twig') }}
</div>
{% endblock %} {% endblock %}

View File

@ -1,23 +1,23 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}User index{% endblock %} {% block title %}Liste des utilisateurs{% endblock %}
{% block body %} {% block body %}
<h1>User index</h1> <h1>Liste des utilisateurs</h1>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>Id</th> <th>Id</th>
<th>Email</th> <th>Email</th>
<th>FirstName</th> <th>Prénom</th>
<th>LastName</th> <th>Nom</th>
<th>BirthDate</th> <th>Date de naissance</th>
<th>Phone</th> <th>Téléphone</th>
<th>Roles</th> <th>Rôles</th>
<th>Password</th> <th>Compétences</th>
<th>actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user in users %} {% for user in users %}
@ -28,20 +28,38 @@
<td>{{ user.LastName }}</td> <td>{{ user.LastName }}</td>
<td>{{ user.BirthDate ? user.BirthDate|date('Y-m-d') : '' }}</td> <td>{{ user.BirthDate ? user.BirthDate|date('Y-m-d') : '' }}</td>
<td>{{ user.Phone }}</td> <td>{{ user.Phone }}</td>
<td>{{ user.roles ? user.roles|json_encode : '' }}</td>
<td>{{ user.password }}</td>
<td> <td>
<a href="{{ path('app_user_show', {'id': user.id}) }}">show</a> {% if user.roles %}
<a href="{{ path('app_user_edit', {'id': user.id}) }}">edit</a> <ul>
{% for role in user.roles %}
<li>{{ role }}</li>
{% endfor %}
</ul>
{% endif %}
</td>
<td>
{% if user.skills|length > 0 %}
<ul>
{% for skill in user.skills %}
<li>{{ skill.Wording }}</li>
{% endfor %}
</ul>
{% else %}
Aucune compétence
{% endif %}
</td>
<td>
<a href="{{ path('app_user_show', {'id': user.id}) }}" class="btn btn-info btn-sm">Voir</a>
<a href="{{ path('app_user_edit', {'id': user.id}) }}" class="btn btn-secondary btn-sm">Modifier</a>
</td> </td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="9">no records found</td> <td colspan="9">Aucun utilisateur trouvé</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_user_new') }}">Create new</a> <a href="{{ path('app_user_new') }}" class="btn btn-success">Créer un nouvel utilisateur</a>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,13 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}New User{% endblock %} {% block title %}Nouveau utilisateur{% endblock %}
{% block body %} {% block body %}
<h1>Create new User</h1> <h1 class="mb-4"> Créer un nouvel utilisateur</h1>
{{ include('user/_form.html.twig') }} {{ include('user/_form.html.twig') }}
<a href="{{ path('app_user_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_user_index') }}" class="btn btn-secondary">← Retour à la liste des utilisateurs</a>
</div>
{% endblock %} {% endblock %}

View File

@ -1,50 +1,67 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}User{% endblock %} {% block title %}Utilisateur{% endblock %}
{% block body %} {% block body %}
<h1>User</h1> <h1>Détails de l'utilisateur</h1>
<table class="table"> <table class="table">
<tbody> <tbody>
<tr> <tr>
<th>Id</th> <th>Id</th>
<td>{{ user.id }}</td> <td>{{ user.id }}</td>
</tr> </tr>
<tr> <tr>
<th>Email</th> <th>Email</th>
<td>{{ user.email }}</td> <td>{{ user.email }}</td>
</tr> </tr>
<tr> <tr>
<th>FirstName</th> <th>Prénom</th>
<td>{{ user.FirstName }}</td> <td>{{ user.FirstName }}</td>
</tr> </tr>
<tr> <tr>
<th>LastName</th> <th>Nom</th>
<td>{{ user.LastName }}</td> <td>{{ user.LastName }}</td>
</tr> </tr>
<tr> <tr>
<th>BirthDate</th> <th>Date de naissance</th>
<td>{{ user.BirthDate ? user.BirthDate|date('Y-m-d') : '' }}</td> <td>{{ user.BirthDate ? user.BirthDate|date('Y-m-d') : '' }}</td>
</tr> </tr>
<tr> <tr>
<th>Phone</th> <th>Téléphone</th>
<td>{{ user.Phone }}</td> <td>{{ user.Phone }}</td>
</tr> </tr>
<tr> <tr>
<th>Roles</th> <th>Rôles</th>
<td>{{ user.roles ? user.roles|json_encode : '' }}</td> <td>
</tr> {% if user.roles %}
<tr> <ul>
<th>Password</th> {% for role in user.roles %}
<td>{{ user.password }}</td> <li>{{ role }}</li>
</tr> {% endfor %}
</ul>
{% endif %}
</td>
</tr>
<tr>
<th>Compétences</th>
<td>
{% if user.skills|length > 0 %}
<ul>
{% for skill in user.skills %}
<li>{{ skill.Wording }}</li>
{% endfor %}
</ul>
{% else %}
Aucune compétence
{% endif %}
</td>
</tr>
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_user_index') }}">back to list</a> <a href="{{ path('app_user_index') }}" class="btn btn-primary">Retour à la liste</a>
<a href="{{ path('app_user_edit', {'id': user.id}) }}" class="btn btn-warning">Modifier</a>
<a href="{{ path('app_user_edit', {'id': user.id}) }}">edit</a>
{{ include('user/_delete_form.html.twig') }} {{ include('user/_delete_form.html.twig') }}
{% endblock %} {% endblock %}

View File

@ -1,13 +1,14 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Edit Vehicle{% endblock %} {% block title %}Modifier un véhicule{% endblock %}
{% block body %} {% block body %}
<h1>Edit Vehicle</h1> <h1 class="mb-4">✏️ Modifier un véhicule</h1>
{{ include('vehicle/_form.html.twig', {'button_label': 'Update'}) }} {{ include('vehicle/_form.html.twig', {'button_label': 'Mettre à jour'}) }}
<a href="{{ path('app_vehicle_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_vehicle_index') }}" class="btn btn-secondary">← Retour à la liste</a>
{{ include('vehicle/_delete_form.html.twig') }} {{ include('vehicle/_delete_form.html.twig') }}
</div>
{% endblock %} {% endblock %}

View File

@ -1,18 +1,19 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Vehicle index{% endblock %} {% block title %}Liste des véhicules{% endblock %}
{% block body %} {% block body %}
<h1>Vehicle index</h1> <h1 class="mb-4">🚗 Liste des véhicules</h1>
<table class="table"> <table class="table table-striped table-bordered align-middle">
<thead> <thead class="table-dark">
<tr> <tr>
<th>Id</th> <th>ID</th>
<th>LicensePlate</th> <th>Immatriculation</th>
<th>Brand</th> <th>Marque</th>
<th>actions</th> <th>Modèle</th>
</tr> <th class="text-center">Actions</th>
</tr>
</thead> </thead>
<tbody> <tbody>
{% for vehicle in vehicles %} {% for vehicle in vehicles %}
@ -20,18 +21,19 @@
<td>{{ vehicle.id }}</td> <td>{{ vehicle.id }}</td>
<td>{{ vehicle.LicensePlate }}</td> <td>{{ vehicle.LicensePlate }}</td>
<td>{{ vehicle.Brand }}</td> <td>{{ vehicle.Brand }}</td>
<td> <td>{{ vehicle.Model }}</td>
<a href="{{ path('app_vehicle_show', {'id': vehicle.id}) }}">show</a> <td class="text-center">
<a href="{{ path('app_vehicle_edit', {'id': vehicle.id}) }}">edit</a> <a href="{{ path('app_vehicle_show', {'id': vehicle.id}) }}" class="btn btn-info btn-sm">👁️ Voir</a>
<a href="{{ path('app_vehicle_edit', {'id': vehicle.id}) }}" class="btn btn-warning btn-sm">✏️ Modifier</a>
</td> </td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="4">no records found</td> <td colspan="5" class="text-center text-muted">Aucun véhicule enregistré.</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_vehicle_new') }}">Create new</a> <a href="{{ path('app_vehicle_new') }}" class="btn btn-success mt-3"> Ajouter un véhicule</a>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,13 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}New Vehicle{% endblock %} {% block title %}Nouveau véhicule{% endblock %}
{% block body %} {% block body %}
<h1>Create new Vehicle</h1> <h1 class="mb-4"> Ajouter un nouveau véhicule</h1>
{{ include('vehicle/_form.html.twig') }} {{ include('vehicle/_form.html.twig') }}
<a href="{{ path('app_vehicle_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_vehicle_index') }}" class="btn btn-secondary">← Retour à la liste</a>
</div>
{% endblock %} {% endblock %}

View File

@ -1,30 +1,34 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %}Vehicle{% endblock %} {% block title %}Détail du véhicule{% endblock %}
{% block body %} {% block body %}
<h1>Vehicle</h1> <h1 class="mb-4">🔍 Détail du véhicule</h1>
<table class="table"> <table class="table table-bordered">
<tbody> <tbody>
<tr> <tr>
<th>Id</th> <th>ID</th>
<td>{{ vehicle.id }}</td> <td>{{ vehicle.id }}</td>
</tr> </tr>
<tr> <tr>
<th>LicensePlate</th> <th>Immatriculation</th>
<td>{{ vehicle.LicensePlate }}</td> <td>{{ vehicle.LicensePlate }}</td>
</tr> </tr>
<tr> <tr>
<th>Brand</th> <th>Marque</th>
<td>{{ vehicle.Brand }}</td> <td>{{ vehicle.Brand }}</td>
</tr> </tr>
<tr>
<th>Modèle</th>
<td>{{ vehicle.Model }}</td>
</tr>
</tbody> </tbody>
</table> </table>
<a href="{{ path('app_vehicle_index') }}">back to list</a> <div class="mt-3">
<a href="{{ path('app_vehicle_index') }}" class="btn btn-secondary">← Retour à la liste</a>
<a href="{{ path('app_vehicle_edit', {'id': vehicle.id}) }}">edit</a> <a href="{{ path('app_vehicle_edit', {'id': vehicle.id}) }}" class="btn btn-warning">✏️ Modifier</a>
{{ include('vehicle/_delete_form.html.twig') }}
{{ include('vehicle/_delete_form.html.twig') }} </div>
{% endblock %} {% endblock %}