diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 73918af..1fa549b 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -8,7 +8,7 @@ security: app_account_provider: entity: class: App\Entity\Account - property: username # Utilise le champ 'username' de votre entité Account pour l'authentification + property: email # Utilise le champ 'username' de votre entité Account pour l'authentification firewalls: dev: diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml index 675bce6..868b8bf 100644 --- a/config/packages/vich_uploader.yaml +++ b/config/packages/vich_uploader.yaml @@ -9,6 +9,14 @@ vich_uploader: inject_on_load: true delete_on_update: true delete_on_remove: true + members: + uri_prefix: /storage/members + upload_destination: '%kernel.project_dir%/public/storage/members' + namer: App\VichUploader\Namer\Account\AvatarName # Replaced namer + directory_namer: App\VichUploader\DirectoryNamer\Account\AvatarName + inject_on_load: true + delete_on_update: true + delete_on_remove: true #mappings: # products: # uri_prefix: /images/products diff --git a/migrations/Version20251117134224.php b/migrations/Version20251117134224.php new file mode 100644 index 0000000..522bbb3 --- /dev/null +++ b/migrations/Version20251117134224.php @@ -0,0 +1,32 @@ +addSql('CREATE TABLE members (id SERIAL NOT NULL, pseudo VARCHAR(255) NOT NULL, role VARCHAR(255) NOT NULL, cosplayer BOOLEAN NOT NULL, crosscosplayer BOOLEAN NOT NULL, trans BOOLEAN NOT NULL, orientation VARCHAR(255) NOT NULL, PRIMARY KEY(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('DROP TABLE members'); + } +} diff --git a/migrations/Version20251117135736.php b/migrations/Version20251117135736.php new file mode 100644 index 0000000..71732a4 --- /dev/null +++ b/migrations/Version20251117135736.php @@ -0,0 +1,43 @@ +addSql('ALTER TABLE members ADD member_file_name VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE members ADD member_dimensions JSON DEFAULT NULL'); + $this->addSql('ALTER TABLE members ADD member_size VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE members ADD member_mine_type VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE members ADD member_original_name VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE members ADD update_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN members.update_at IS \'(DC2Type:datetime_immutable)\''); + } + + 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 members DROP member_file_name'); + $this->addSql('ALTER TABLE members DROP member_dimensions'); + $this->addSql('ALTER TABLE members DROP member_size'); + $this->addSql('ALTER TABLE members DROP member_mine_type'); + $this->addSql('ALTER TABLE members DROP member_original_name'); + $this->addSql('ALTER TABLE members DROP update_at'); + } +} diff --git a/src/Controller/Admin/AdminController.php b/src/Controller/Admin/AdminController.php new file mode 100644 index 0000000..f27d2b8 --- /dev/null +++ b/src/Controller/Admin/AdminController.php @@ -0,0 +1,94 @@ + false], methods: ['GET'])] + public function adminDashboard(): Response + { + return $this->render('admin/dashboard.twig', [ + ]); + } + + #[Route(path: '/admin/members', name: 'admin_members', options: ['sitemap' => false], methods: ['GET'])] + public function adminMembers(MembersRepository $membersRepository): Response + { + + return $this->render('admin/members.twig', [ + 'members' => $membersRepository->findBy([],['id' => 'ASC']), + ]); + } + #[Route(path: '/admin/members/{id}', name: 'admin_member_edit', options: ['sitemap' => false], methods: ['GET','POST'])] + public function adminMembersEdit(?Members $members,Request $request,EntityManagerInterface $entityManager): Response + { + $form = $this->createForm(MembersType::class, $members); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($members); + $entityManager->flush(); + return $this->redirectToRoute('admin_members'); + } + return $this->render('admin/member/add.twig', [ + 'form' => $form->createView(), + ]); + } + #[Route(path: '/admin/members/add', name: 'admin_member_create', options: ['sitemap' => false], methods: ['GET','POST'], priority: 5)] + public function adminMembersCreate(Request $request,EntityManagerInterface $entityManager): Response + { + $members = new Members(); + $members->setTrans(false); + $members->setCrosscosplayer(false); + $members->setCosplayer(false); + $form = $this->createForm(MembersType::class, $members); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($members); + $entityManager->flush(); + return $this->redirectToRoute('admin_members'); + } + return $this->render('admin/member/add.twig', [ + 'form' => $form->createView(), + ]); + } + #[Route(path: '/admin/members/delete/{id}', name: 'admin_member_delete', options: ['sitemap' => false], methods: ['GET'])] + public function adminMembersDelete(): Response + { + + } + + #[Route(path: '/admin/events', name: 'admin_events', options: ['sitemap' => false], methods: ['GET'])] + public function adminEvents(): Response + { + return $this->render('admin/dashboard.twig', [ + ]); + } + + #[Route(path: '/admin/account', name: 'admin_accounts_list', options: ['sitemap' => false], methods: ['GET'])] + public function adminAccount(): Response + { + return $this->render('admin/dashboard.twig', [ + ]); + } +} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index d49956c..1a1d296 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -26,6 +26,8 @@ class SecurityController extends AbstractController #[Route(path: '/connexion', name: 'app_login', options: ['sitemap' => false], methods: ['GET','POST'])] public function login(AuthenticationUtils $authenticationUtils): Response { + if($this->getUser()) + return $this->redirectToRoute('app_home'); return $this->render('security/login.twig', [ 'last_username' => $authenticationUtils->getLastUsername(), 'error' => $authenticationUtils->getLastAuthenticationError(), diff --git a/src/Entity/Members.php b/src/Entity/Members.php new file mode 100644 index 0000000..7c8723d --- /dev/null +++ b/src/Entity/Members.php @@ -0,0 +1,257 @@ +id; + } + + public function getPseudo(): ?string + { + return $this->pseudo; + } + + public function setPseudo(string $pseudo): static + { + $this->pseudo = $pseudo; + + return $this; + } + + public function getRole(): ?string + { + return $this->role; + } + + public function setRole(string $role): static + { + $this->role = $role; + + return $this; + } + + public function isCosplayer(): ?bool + { + return $this->cosplayer; + } + + public function setCosplayer(bool $cosplayer): static + { + $this->cosplayer = $cosplayer; + + return $this; + } + + public function isCrosscosplayer(): ?bool + { + return $this->crosscosplayer; + } + + public function setCrosscosplayer(bool $crosscosplayer): static + { + $this->crosscosplayer = $crosscosplayer; + + return $this; + } + + public function isTrans(): ?bool + { + return $this->trans; + } + + public function setTrans(bool $trans): static + { + $this->trans = $trans; + + return $this; + } + + public function getOrientation(): ?string + { + return $this->orientation; + } + + public function setOrientation(string $orientation): static + { + $this->orientation = $orientation; + + return $this; + } + + /** + * @return \DateTimeImmutable|null + */ + public function getUpdateAt(): ?\DateTimeImmutable + { + return $this->updateAt; + } + + /** + * @return bool|null + */ + public function getCosplayer(): ?bool + { + return $this->cosplayer; + } + + /** + * @return bool|null + */ + public function getCrosscosplayer(): ?bool + { + return $this->crosscosplayer; + } + + /** + * @return array|null + */ + public function getMemberDimensions(): ?array + { + return $this->memberDimensions; + } + + /** + * @return string|null + */ + public function getMemberFileName(): ?string + { + return $this->memberFileName; + } + + /** + * @return string|null + */ + public function getMemberMineType(): ?string + { + return $this->memberMineType; + } + + /** + * @return string|null + */ + public function getMemberOriginalName(): ?string + { + return $this->memberOriginalName; + } + + /** + * @return File|null + */ + public function getMembers(): ?File + { + return $this->members; + } + + /** + * @return string|null + */ + public function getMemberSize(): ?string + { + return $this->memberSize; + } + + /** + * @param \DateTimeImmutable|null $updateAt + */ + public function setUpdateAt(?\DateTimeImmutable $updateAt): void + { + $this->updateAt = $updateAt; + } + + /** + * @param array|null $memberDimensions + */ + public function setMemberDimensions(?array $memberDimensions): void + { + $this->memberDimensions = $memberDimensions; + } + + /** + * @param File|null $members + */ + public function setMembers(?File $members): void + { + $this->members = $members; + } + + /** + * @param string|null $memberFileName + */ + public function setMemberFileName(?string $memberFileName): void + { + $this->memberFileName = $memberFileName; + } + + /** + * @param string|null $memberMineType + */ + public function setMemberMineType(?string $memberMineType): void + { + $this->memberMineType = $memberMineType; + } + + /** + * @param string|null $memberOriginalName + */ + public function setMemberOriginalName(?string $memberOriginalName): void + { + $this->memberOriginalName = $memberOriginalName; + } + + /** + * @param string|null $memberSize + */ + public function setMemberSize(?string $memberSize): void + { + $this->memberSize = $memberSize; + } +} diff --git a/src/Form/MembersType.php b/src/Form/MembersType.php new file mode 100644 index 0000000..439f3f0 --- /dev/null +++ b/src/Form/MembersType.php @@ -0,0 +1,94 @@ +add('members', FileType::class, [ + 'label' => 'Photo du membre (Max 2Mo)', + 'required' => false, // Rendre facultatif + 'mapped' => false, // Indiquer que ce champ n'est pas directement mappé à une propriété de l'entité + 'attr' => [ + 'placeholder' => 'Choisir un fichier...', + ] + ]) + ->add('pseudo', TextType::class, [ + 'label' => 'Pseudo du membre', + 'required' => true, + ]) + ->add('role', ChoiceType::class, [ + 'label' => 'Rôle au sein de l\'association', + 'choices' => [ + 'Président(e)' => 'Président(e)', + 'Trésorier(e)' => 'Trésorier(e)', + 'Secrétaire(e)' => 'Secrétaire(e)', + 'Vice-Président(e)' => 'Vice-Président(e)', + 'Trésorier(e) Adjoints' => 'Trésorier(e) Adjoints', + 'Secrétaire(e) Adjoints' => 'Secrétaire(e) Adjoints', + ], + ]) + ->add('orientation', ChoiceType::class, [ + 'label' => 'Orientation (Tag)', + 'choices' => [ + 'Non spécifié' => 'not_specified', + 'Asexuel(le)' => 'asexual', + 'Bisexuel(le)' => 'bisexual', + 'Demisexuel(le)' => 'demisexual', + 'Gay' => 'gay', + 'Hétérosexuel(le)' => 'heterosexual', + 'Lesbienne' => 'lesbian', + 'Pansexuel(le)' => 'pansexual', + 'Queer' => 'queer', + 'En questionnement' => 'questioning', + 'Autre' => 'other', + ], + // Optionnel : affichez-le comme une liste déroulante normale (pas expanded) + ]) + + // Les champs booléens sont mieux affichés comme des boutons radio (expanded) + ->add('crosscosplayer', ChoiceType::class, [ + 'label' => 'Crosscosplayer ?', + 'choices' => [ + 'Non' => false, + 'Oui' => true, + ], + 'expanded' => true, // Affiche comme boutons radio + 'multiple' => false, + ]) + ->add('trans', ChoiceType::class, [ + 'label' => 'Transgenre ?', + 'choices' => [ + 'Non' => false, + 'Oui' => true, + ], + 'expanded' => true, + 'multiple' => false, + ]) + ->add('cosplayer', ChoiceType::class, [ + 'label' => 'Cosplayer ?', + 'choices' => [ + 'Non' => false, + 'Oui' => true, + ], + 'expanded' => true, + 'multiple' => false, + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('data_class', Members::class); + } +} diff --git a/src/Repository/MembersRepository.php b/src/Repository/MembersRepository.php new file mode 100644 index 0000000..39342b3 --- /dev/null +++ b/src/Repository/MembersRepository.php @@ -0,0 +1,43 @@ + + */ +class MembersRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Members::class); + } + + // /** + // * @return Members[] Returns an array of Members objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('m') + // ->andWhere('m.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('m.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Members + // { + // return $this->createQueryBuilder('m') + // ->andWhere('m.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/templates/admin/base.twig b/templates/admin/base.twig new file mode 100644 index 0000000..dca911a --- /dev/null +++ b/templates/admin/base.twig @@ -0,0 +1,129 @@ +{# Assurez-vous d'utiliser une version de Tailwind CSS qui supporte ces classes #} + + + + + + + {# 🛑 NO INDEX DIRECTIVE #} + + {% block title %}Admin Dashboard{% endblock %} | Mon App + + {# 🎨 Tailwind CSS Integration (Utilisation du CDN pour la démo) #} + + {% block stylesheets %}{% endblock %} + + +{# Utiliser un layout en grille ou flex pour organiser la sidebar et le contenu #} + + +
+ + {# 1. BARRE LATÉRALE (SIDEBAR - FOND BLANC) #} + + + {# 2. CONTENU PRINCIPAL (Inclut la Topbar) #} +
+ + {# 3. TOP BAR (BARRE SUPÉRIEURE) #} +
+ + {# Remplacer 'current_user.name' par la variable Twig de votre session #} +
+ Bienvenue, {{ app.user.username }} +
+ + {# Vous pourriez ajouter ici un bouton de profil ou un dropdown #} +
+ + {# ZONE DE CONTENU PRINCIPALE #} +
+

+ {% block page_title %}{% endblock %} +

+ +
+ {% block body %} + + {% endblock %} +
+
+
+
+ +{% block javascripts %}{% endblock %} + + diff --git a/templates/admin/dashboard.twig b/templates/admin/dashboard.twig new file mode 100644 index 0000000..e5e814b --- /dev/null +++ b/templates/admin/dashboard.twig @@ -0,0 +1,3 @@ +{% extends 'admin/base.twig' %} +{% block title %}Tableau de bord{% endblock %} +{% block page_title %}Tableau de bord{% endblock %} diff --git a/templates/admin/member/add.twig b/templates/admin/member/add.twig new file mode 100644 index 0000000..ca61503 --- /dev/null +++ b/templates/admin/member/add.twig @@ -0,0 +1,79 @@ +{% extends 'admin/base.twig' %} +{% block title %}Ajouter/Éditer un Membre{% endblock %} +{% block page_title %} + {{ form.vars.value.id ? 'Éditer le Membre' : 'Créer un nouveau Membre' }} +{% endblock %} + +{% block body %} + +
+ + {{ form_start(form, {'attr': {'class': 'space-y-6'}}) }} + + {# --- SECTION 1: Informations de base --- #} +

Informations Principales

+ +
+ + {# Pseudo #} + {{ form_row(form.pseudo, {'attr': {'class': 'w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500'}}) }} + + {# Rôle #} + {{ form_row(form.role, {'attr': {'class': 'w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500'}}) }} +
+ {{ form_row(form.members, { + 'label': 'Photo', + 'attr': {'class': 'block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 focus:outline-none p-2'} + }) }} + {# Orientation #} +
+ {{ form_row(form.orientation, {'attr': {'class': 'w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500'}}) }} +
+ + + {# --- SECTION 2: Tags / Booléens (Boutons Radio) --- #} +

Tags et Caractéristiques

+ +
+ + {# Crosscosplayer #} + {{ form_row(form.crosscosplayer, { + 'label': 'Crosscosplayer ?', + 'row_attr': {'class': 'space-y-4'}, + 'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'} + }) }} + + {# Cosplayer #} + {{ form_row(form.cosplayer, { + 'label': 'Cosplayer ?', + 'row_attr': {'class': 'space-y-4'}, + 'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'} + }) }} + + {# Trans #} + {{ form_row(form.trans, { + 'label': 'Transgenre ?', + 'row_attr': {'class': 'space-y-4'}, + 'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'} + }) }} + +
+ + {# --- Bouton de Soumission --- #} +
+ +
+ + {{ form_end(form) }} + +
+ + {# --- Rendu des erreurs et champs cachés --- #} + {{ form_end(form) }} + +{% endblock %} diff --git a/templates/admin/members.twig b/templates/admin/members.twig new file mode 100644 index 0000000..f677297 --- /dev/null +++ b/templates/admin/members.twig @@ -0,0 +1,79 @@ +{% extends 'admin/base.twig' %} +{% block title %}Membre(s){% endblock %} +{% block page_title %}Liste des Membres{% endblock %} + +{% block body %} + +
+ + {# Vous pouvez ajouter ici une icône si vous le souhaitez, par exemple : [Icône] #} + Créer un membre + +
+
+ + + {# --- EN-TÊTE DU TABLEAU (HEAD) --- #} + + + + + + + + + {# --- CORPS DU TABLEAU (BODY) --- #} + + + {# Démonstration: Boucle sur une liste de membres (members) passée par votre contrôleur #} + {% if members is not empty %} + {% for member in members %} + + + + + + {% endfor %} + {% else %} + {# Message si la liste est vide #} + + + + {% endif %} + + +
+ Pseudo + + Rôle + + Actions +
+
{{ member.pseudo }}
+
+ {# Utilisation d'un badge Tailwind pour le rôle #} + + {{ member.role }} + + + + Éditer + + + Supprimer + +
+ Aucun membre trouvé. +
+
+ +{% endblock %} diff --git a/templates/base.twig b/templates/base.twig index 1abce05..1b853d8 100644 --- a/templates/base.twig +++ b/templates/base.twig @@ -186,6 +186,9 @@
{{ 'logged_in_as'|trans }} {{ app.user.username|default('Compte') }}
+ + {{ 'logged_admin'|trans }} + {{ 'logout_link'|trans }} diff --git a/templates/security/login.twig b/templates/security/login.twig index 09e60d4..6d4cf95 100644 --- a/templates/security/login.twig +++ b/templates/security/login.twig @@ -38,7 +38,7 @@ {# Display error messages if login fails #} {% if error %} {% endif %} {% for message in app.flashes('success') %} diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index b8c9bc2..0892f16 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -567,3 +567,4 @@ open_user_menu_sr: Open user menu logged_in_as: Signed in as logout_link: Log Out page.login: Login +logged_admin: Administration diff --git a/translations/messages.fr.yaml b/translations/messages.fr.yaml index ed98748..ee0540f 100644 --- a/translations/messages.fr.yaml +++ b/translations/messages.fr.yaml @@ -509,3 +509,4 @@ open_user_menu_sr: Ouvrir le menu utilisateur logged_in_as: Connecté en tant que logout_link: Déconnexion page.login: Connexion +logged_admin: Administration