set admin V2
This commit is contained in:
1
.idea/sortir.iml
generated
1
.idea/sortir.iml
generated
@@ -3,6 +3,7 @@
|
|||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
|
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
|
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
background-color: #f8f9fa;
|
background-color: #2a8d57;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.star {
|
.star {
|
||||||
|
|||||||
@@ -2,7 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Participant;
|
||||||
|
use App\Entity\Ville;
|
||||||
|
use App\Repository\ParticipantRepository;
|
||||||
|
use App\Repository\VilleRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
@@ -15,11 +23,120 @@ class AdminController extends AbstractController
|
|||||||
'controller_name' => 'AdminController',
|
'controller_name' => 'AdminController',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Gestion des utilisateurs
|
||||||
#[Route('/admin/user', name: 'app_adminUser')]
|
#[Route('/admin/user', name: 'app_adminUser')]
|
||||||
public function adminUser(): Response
|
public function adminUser(ParticipantRepository $participantRepository): Response
|
||||||
{
|
{
|
||||||
return $this->render('admin/user.html.twig', [
|
return $this->render('admin/user.html.twig', [
|
||||||
|
'participants' => $participantRepository->findAll(),
|
||||||
'controller_name' => 'AdminController',
|
'controller_name' => 'AdminController',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
#[Route('/admin/user/import', name: 'participant_import', methods: ['POST'])]
|
||||||
|
public function import(Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$file = $request->files->get('csv_file');
|
||||||
|
if ($file) {
|
||||||
|
$csvData = array_map('str_getcsv', file($file->getPathname()));
|
||||||
|
foreach ($csvData as $index => $row) {
|
||||||
|
if ($index === 0) continue;
|
||||||
|
$participant = new Participant();
|
||||||
|
$participant->setNom($row[0]);
|
||||||
|
$participant->setPrenom($row[1]);
|
||||||
|
$participant->setTelephone($row[2]);
|
||||||
|
$participant->setEmail($row[3]);
|
||||||
|
$participant->setAdministrateur((bool)$row[4]);
|
||||||
|
$participant->setActif((bool)$row[5]);
|
||||||
|
$participant->setRoles(explode('|', $row[6]));
|
||||||
|
$participant->setPassword(password_hash($row[7], PASSWORD_BCRYPT));
|
||||||
|
$em->persist($participant);
|
||||||
|
}
|
||||||
|
$em->flush();
|
||||||
|
}
|
||||||
|
return $this->redirectToRoute('participant_index');
|
||||||
|
}
|
||||||
|
#[Route('/admin/user/export', name: 'participant_export')]
|
||||||
|
public function export(ParticipantRepository $participantRepository): Response
|
||||||
|
{
|
||||||
|
$participants = $participantRepository->findAll();
|
||||||
|
$csv = "Nom,Prénom,Téléphone,Email,Administrateur,Actif,Rôles,Password\n";
|
||||||
|
foreach ($participants as $participant) {
|
||||||
|
$csv .= sprintf(
|
||||||
|
"%s,%s,%s,%s,%s,%s,%s,%s\n",
|
||||||
|
$participant->getNom(),
|
||||||
|
$participant->getPrenom(),
|
||||||
|
$participant->getTelephone(),
|
||||||
|
$participant->getMail(),
|
||||||
|
$participant->isAdministrateur() ? '1' : '0',
|
||||||
|
$participant->isActif() ? '1' : '0',
|
||||||
|
implode('|', $participant->getRoles()),
|
||||||
|
$participant->getPassword()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$response = new Response($csv);
|
||||||
|
$response->headers->set('Content-Type', 'text/csv');
|
||||||
|
$response->headers->set('Content-Disposition', 'attachment;filename="participants.csv"');
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Gestion des villes
|
||||||
|
#[Route('/admin/city', name: 'app_adminCity')]
|
||||||
|
public function adminCity(VilleRepository $villeRepository): Response
|
||||||
|
{
|
||||||
|
return $this->render('admin/city.html.twig', [
|
||||||
|
'citys' => $villeRepository->findAll(),
|
||||||
|
'controller_name' => 'AdminController',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
#[Route('/admin/city/add', name: 'app_adminCityAdd', methods: ['POST'])]
|
||||||
|
public function adminCityAdd(Request $request, EntityManagerInterface $entityManager, VilleRepository $villeRepository): RedirectResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Récupérer les données envoyées par le formulaire
|
||||||
|
$postalCode = $request->request->get('postalCode');
|
||||||
|
$cityName = $request->request->get('citySelect');
|
||||||
|
|
||||||
|
// Vérifier que les champs ne sont pas vides
|
||||||
|
if (!$postalCode || !$cityName) {
|
||||||
|
return new Response('Tous les champs sont requis.', Response::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer une nouvelle entité City et définir ses propriétés
|
||||||
|
$city = new Ville();
|
||||||
|
$city->setNom($cityName);
|
||||||
|
$city->setCodePostal($postalCode);
|
||||||
|
|
||||||
|
// Enregistrer la ville dans la base de données
|
||||||
|
$entityManager->persist($city);
|
||||||
|
$entityManager->flush();
|
||||||
|
$this->addFlash('success', "Ville ajouté !");
|
||||||
|
return $this->redirectToRoute('app_adminCity');
|
||||||
|
} catch(\Exception $e) {
|
||||||
|
$this->addFlash('error', "Erreur : " . $e->getMessage());
|
||||||
|
return $this->redirectToRoute('app_adminCity');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[Route('/admin/city/delete', name: 'app_adminCityDelete')]
|
||||||
|
public function adminCityDelete(String $id, EntityManagerInterface $entityManager): RedirectResponse
|
||||||
|
{
|
||||||
|
// Récupérer la ville à supprimer
|
||||||
|
$city = $entityManager->getRepository(Ville::class)->find($id);
|
||||||
|
|
||||||
|
// Vérifier si la ville existe
|
||||||
|
if (!$city) {
|
||||||
|
// Si la ville n'existe pas, rediriger avec un message d'erreur
|
||||||
|
$this->addFlash('error', 'La ville demandée n\'existe pas.');
|
||||||
|
return $this->redirectToRoute('app_adminCity'); // Rediriger vers la liste des villes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer la ville
|
||||||
|
$entityManager->remove($city);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
// Ajouter un message de succès et rediriger vers la liste des villes
|
||||||
|
$this->addFlash('success', 'Ville supprimée avec succès.');
|
||||||
|
return $this->redirectToRoute('app_adminCity');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
119
templates/admin/city.html.twig
Normal file
119
templates/admin/city.html.twig
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
{% extends 'main/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}📣 Sortie.com Admin City 🔊{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="flex">
|
||||||
|
{% include 'admin/sidebar.html.twig' %}
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="ml-64 p-8 w-full">
|
||||||
|
<h1 class="text-2xl font-semibold mb-4">Gestion des villes</h1>
|
||||||
|
|
||||||
|
<!-- Actions: Ajouter -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<!-- Bouton pour ouvrir la modale -->
|
||||||
|
<button id="openModal" class="inline-block bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">
|
||||||
|
Ajouter une ville
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Participants Table -->
|
||||||
|
<div class="overflow-x-auto bg-white rounded shadow">
|
||||||
|
<table class="min-w-full bg-white divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nom</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Code postal</th>
|
||||||
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200">
|
||||||
|
{% for city in citys %}
|
||||||
|
<tr>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ city.nom }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ city.codePostal }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
|
<a href="{{ path('app_adminCityDelete', {'id': city.idVille}) }}" class="text-red-600 hover:text-red-900 ml-4">Supprimer</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="8" class="px-6 py-4 text-center text-gray-500">Aucune ville trouvée</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modale pour ajouter une ville -->
|
||||||
|
<div id="cityModal" class="fixed inset-0 z-50 hidden bg-gray-900 bg-opacity-50">
|
||||||
|
<div class="flex justify-center items-center min-h-screen">
|
||||||
|
<div class="bg-white p-6 rounded shadow-md w-1/3">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Ajouter une ville</h2>
|
||||||
|
<form id="addCityForm" method="POST" action="{{ path('app_adminCityAdd') }}">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="postalCode" class="block text-sm font-medium text-gray-700">Code postal</label>
|
||||||
|
<input id="postalCode" name="postalCode" type="text" class="mt-1 block w-full px-4 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="citySelect" class="block text-sm font-medium text-gray-700">Sélectionner une ville</label>
|
||||||
|
<select id="citySelect" name="citySelect" class="mt-1 block w-full px-4 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" required>
|
||||||
|
<!-- Options will be populated dynamically based on postal code -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700">Ajouter</button>
|
||||||
|
<button type="button" id="closeModal" class="ml-2 bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-700">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Ouvrir la modale
|
||||||
|
document.getElementById('openModal').addEventListener('click', function() {
|
||||||
|
document.getElementById('cityModal').classList.remove('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fermer la modale
|
||||||
|
document.getElementById('closeModal').addEventListener('click', function() {
|
||||||
|
document.getElementById('cityModal').classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fonction pour charger les villes en fonction du code postal en utilisant l'API Carto
|
||||||
|
document.getElementById('postalCode').addEventListener('input', function() {
|
||||||
|
const postalCode = this.value;
|
||||||
|
const citySelect = document.getElementById('citySelect');
|
||||||
|
|
||||||
|
if (postalCode.length >= 3) {
|
||||||
|
// URL de l'API Carto pour récupérer les villes en fonction du code postal
|
||||||
|
const apiUrl = `https://api-adresse.data.gouv.fr/search/?q=${postalCode}&type=municipality&limit=10`;
|
||||||
|
|
||||||
|
fetch(apiUrl)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// Clear previous options
|
||||||
|
citySelect.innerHTML = '';
|
||||||
|
|
||||||
|
// Ajouter des nouvelles options de villes dans la liste déroulante
|
||||||
|
data.features.forEach(feature => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = feature.properties.label;
|
||||||
|
option.textContent = feature.properties.label; // Nom de la ville
|
||||||
|
citySelect.appendChild(option);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Erreur:', error));
|
||||||
|
} else {
|
||||||
|
// Clear options si le code postal est trop court
|
||||||
|
citySelect.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -7,12 +7,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" class="block text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded">
|
<a href="{{ path('app_adminUser') }}" class="block text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded">
|
||||||
👤 Utilisateurs
|
👤 Utilisateurs
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" class="block text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded">
|
<a href="{{ path('app_adminCity') }}" class="block text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded">
|
||||||
🏙 Villes
|
🏙 Villes
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
83
templates/admin/user.html.twig
Normal file
83
templates/admin/user.html.twig
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
{% extends 'main/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}📣 Sortie.com Admin User 🔊{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="flex">
|
||||||
|
{% include 'admin/sidebar.html.twig' %}
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="ml-64 p-8 w-full">
|
||||||
|
<h1 class="text-2xl font-semibold mb-4">Gestion des utilisateurs</h1>
|
||||||
|
|
||||||
|
<!-- Actions: Import/Export -->
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<form action="{{ path('participant_import') }}" method="post" enctype="multipart/form-data" class="flex items-center">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
name="csv_file"
|
||||||
|
accept=".csv"
|
||||||
|
class="block w-full text-sm text-gray-500
|
||||||
|
file:mr-4 file:py-2 file:px-4
|
||||||
|
file:rounded file:border-0
|
||||||
|
file:text-sm file:font-semibold
|
||||||
|
file:bg-gray-800 file:text-white
|
||||||
|
hover:file:bg-gray-700" />
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
|
||||||
|
Importer CSV
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<a href="{{ path('participant_export') }}" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
|
||||||
|
Exporter CSV
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Participants Table -->
|
||||||
|
<div class="overflow-x-auto bg-white rounded shadow">
|
||||||
|
<table class="min-w-full bg-white divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nom</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Prénom</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Téléphone</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Administrateur</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actif</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Rôles</th>
|
||||||
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200">
|
||||||
|
{% for participant in participants %}
|
||||||
|
<tr>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ participant.nom }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ participant.prenom }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ participant.telephone }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ participant.mail }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
{{ participant.administrateur ? 'Oui' : 'Non' }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
{{ participant.actif ? 'Oui' : 'Non' }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
{{ participant.roles|join(', ') }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
|
<a href="{{ path('participant_edit', {'id': participant.id}) }}" class="text-indigo-600 hover:text-indigo-900">Modifier</a>
|
||||||
|
<a href="{{ path('participant_delete', {'id': participant.id}) }}" class="text-red-600 hover:text-red-900 ml-4">Supprimer</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="8" class="px-6 py-4 text-center text-gray-500">Aucun participant trouvé</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="flex items-center justify-center" style="background-color: #F4F4F4;" >
|
<div class="flex items-center justify-center" >
|
||||||
ENI © Sortie {{ "now"|date("Y") }}
|
ENI © Sortie {{ "now"|date("Y") }}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user