Un peu de tout

This commit is contained in:
mepiphana2023
2024-04-29 12:54:16 +02:00
parent 2791df65ea
commit 9c5ece84cc
12 changed files with 238 additions and 71 deletions

View File

@@ -40,6 +40,9 @@ dependencies {
//test //test
testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
//data
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
} }
tasks.named('test') { tasks.named('test') {

View File

@@ -2,6 +2,8 @@ package fr.eni.enchere.bll;
import fr.eni.enchere.bo.Article; import fr.eni.enchere.bo.Article;
import fr.eni.enchere.bo.SearchArticleCritere; import fr.eni.enchere.bo.SearchArticleCritere;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List; import java.util.List;
@@ -13,5 +15,5 @@ public interface ArticleService {
void deleteArticle(int id); void deleteArticle(int id);
void updateArticle(int id); void updateArticle(int id);
List<Article> findArticleByTitle(String title); List<Article> findArticleByTitle(String title);
List<Article> searchArticle(SearchArticleCritere critere); Page<Article> searchArticlePageable(SearchArticleCritere critere, Pageable pageable);
} }

View File

@@ -3,6 +3,8 @@ package fr.eni.enchere.bll;
import fr.eni.enchere.bo.Article; import fr.eni.enchere.bo.Article;
import fr.eni.enchere.bo.SearchArticleCritere; import fr.eni.enchere.bo.SearchArticleCritere;
import fr.eni.enchere.dal.ArticleRepository; import fr.eni.enchere.dal.ArticleRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
@@ -45,8 +47,9 @@ public class ArticleServiceImpl implements ArticleService{
return articleRepository.findArticleByTitle(title); return articleRepository.findArticleByTitle(title);
} }
@Override @Override
public List<Article> searchArticle(SearchArticleCritere critere) { public Page<Article> searchArticlePageable(SearchArticleCritere critere, Pageable pageable) {
return articleRepository.searchArticle(critere); return articleRepository.searchArticlePageable(critere, pageable);
} }
} }

View File

@@ -10,6 +10,8 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@@ -44,7 +46,7 @@ public class AccueilController {
} }
@GetMapping({"/", "/accueil"}) @GetMapping({"/", "/accueil"})
public String viewAccueil(HttpServletRequest request, @AuthenticationPrincipal UserDetails userDetails, @RequestParam(required = false) String searchTitle, @RequestParam(required = false) Integer searchCategory, Model model, @RequestParam(value = "venteOption", required = false) String[] venteOptions, @RequestParam(value = "achatOption", required = false) String[] achatOptions) { public String viewAccueil(HttpServletRequest request, @AuthenticationPrincipal UserDetails userDetails, @RequestParam(required = false) String searchTitle, @RequestParam(required = false) Integer searchCategory, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "6") int size, Model model, @RequestParam(value = "venteOption", required = false) String[] venteOptions, @RequestParam(value = "achatOption", required = false) String[] achatOptions) {
model.addAttribute("categories", categorieService.findAllCategories()); model.addAttribute("categories", categorieService.findAllCategories());
model.addAttribute("requestURI", request.getRequestURI()); model.addAttribute("requestURI", request.getRequestURI());
SearchArticleCritere critere = new SearchArticleCritere(); SearchArticleCritere critere = new SearchArticleCritere();
@@ -55,15 +57,21 @@ public class AccueilController {
} }
critere.setVenteOptions(venteOptions); critere.setVenteOptions(venteOptions);
critere.setAchatOptions(achatOptions); critere.setAchatOptions(achatOptions);
model.addAttribute("articles", articleService.searchArticle(critere));
// Pagination
Page<Article> articlePage = articleService.searchArticlePageable(critere, PageRequest.of(page, size));
model.addAttribute("articles", articlePage.getContent());
model.addAttribute("currentPage", page);
model.addAttribute("totalPages", articlePage.getTotalPages());
return "accueil"; return "accueil";
} }
@PostMapping("/accueil") @PostMapping("/accueil")
public String handleSearch(HttpServletRequest request, @AuthenticationPrincipal UserDetails userDetails, @RequestParam("searchTitle") String searchTitle, @RequestParam(value = "searchCategory", required = false) Integer searchCategory, Model model, @RequestParam(value = "venteOption", required = false) String[] venteOptions, @RequestParam(value = "achatOption", required = false) String[] achatOptions ) { public String handleSearch(HttpServletRequest request, @AuthenticationPrincipal UserDetails userDetails, @RequestParam(required = false) String searchTitle, @RequestParam(required = false) Integer searchCategory, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "6") int size, Model model, @RequestParam(value = "venteOption", required = false) String[] venteOptions, @RequestParam(value = "achatOption", required = false) String[] achatOptions) {
return viewAccueil(request, userDetails, searchTitle, searchCategory, model, venteOptions, achatOptions); return viewAccueil(request, userDetails, searchTitle, searchCategory, page, size, model, venteOptions, achatOptions);
} }

View File

@@ -23,7 +23,6 @@ public class LanguageController {
@GetMapping("/change-language") @GetMapping("/change-language")
public String changeLanguage(HttpServletRequest request, HttpServletResponse response, Locale locale) { public String changeLanguage(HttpServletRequest request, HttpServletResponse response, Locale locale) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request); LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
System.out.println(locale.getLanguage());
if (localeResolver != null) { if (localeResolver != null) {
if (locale.getLanguage().equals("en")) { if (locale.getLanguage().equals("en")) {
localeResolver.setLocale(request, response, Locale.FRENCH); // Changer la langue en français localeResolver.setLocale(request, response, Locale.FRENCH); // Changer la langue en français
@@ -31,7 +30,15 @@ public class LanguageController {
localeResolver.setLocale(request, response, Locale.ENGLISH); // Changer la langue en anglais localeResolver.setLocale(request, response, Locale.ENGLISH); // Changer la langue en anglais
} }
} }
return "redirect:/";
String referer = request.getHeader("Referer");
if (referer != null && !referer.isEmpty()) {
return "redirect:" + referer;
} else {
return "redirect:/";
}
} }
} }

View File

@@ -2,12 +2,14 @@ package fr.eni.enchere.dal;
import fr.eni.enchere.bo.Article; import fr.eni.enchere.bo.Article;
import fr.eni.enchere.bo.SearchArticleCritere; import fr.eni.enchere.bo.SearchArticleCritere;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List; import java.util.List;
public interface ArticleRepository { public interface ArticleRepository {
List<Article> findAllArticle(); List<Article> findAllArticle();
List<Article> searchArticle(SearchArticleCritere critere); Page<Article> searchArticlePageable(SearchArticleCritere critere, Pageable pageable);
Article findArticleById(int id); Article findArticleById(int id);
List<Article> findArticleByTitle(String title); List<Article> findArticleByTitle(String title);
int saveArticle(Article article); int saveArticle(Article article);

View File

@@ -8,6 +8,9 @@ import fr.eni.enchere.controllers.AccueilController;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@@ -87,7 +90,7 @@ public class ArticleRepositoryImpl implements ArticleRepository {
} }
@Override @Override
public List<Article> searchArticle(SearchArticleCritere critere) { public Page<Article> searchArticlePageable(SearchArticleCritere critere, Pageable pageable) {
StringBuilder sql = new StringBuilder("SELECT DISTINCT a.*, u.* FROM ARTICLES_VENDUS a "); StringBuilder sql = new StringBuilder("SELECT DISTINCT a.*, u.* FROM ARTICLES_VENDUS a ");
sql.append("JOIN UTILISATEURS u ON a.no_utilisateur = u.no_utilisateur "); sql.append("JOIN UTILISATEURS u ON a.no_utilisateur = u.no_utilisateur ");
sql.append("LEFT JOIN ENCHERES e ON a.no_article = e.no_article "); sql.append("LEFT JOIN ENCHERES e ON a.no_article = e.no_article ");
@@ -165,8 +168,104 @@ public class ArticleRepositoryImpl implements ArticleRepository {
sql.append(")"); sql.append(")");
} }
System.out.println();
// Compte le nombre total d'articles
int totalCount = countArticlePageable(critere);
// Ajoute la pagination à la requête SQL
sql.append(" LIMIT ? OFFSET ?");
params.add(pageable.getPageSize());
params.add(pageable.getOffset());
// Exécute la requête paginée
List<Article> articles = jdbcTemplate.query(sql.toString(), new HomeArticleRowMapper(), params.toArray()); List<Article> articles = jdbcTemplate.query(sql.toString(), new HomeArticleRowMapper(), params.toArray());
// Crée une Page<Article> à partir des résultats et des informations de pagination
return new PageImpl<>(articles, pageable, totalCount);
}
public int countArticlePageable(SearchArticleCritere critere) {
StringBuilder sql = new StringBuilder("SELECT COUNT(a.no_article) FROM ARTICLES_VENDUS a ");
sql.append("JOIN UTILISATEURS u ON a.no_utilisateur = u.no_utilisateur ");
sql.append("LEFT JOIN ENCHERES e ON a.no_article = e.no_article ");
sql.append("WHERE 1 = 1 AND a.isDelete = 0");
List<Object> params = new ArrayList<>();
if (critere.getNoCategorie() != null) {
sql.append(" AND a.no_categorie = ?");
params.add(critere.getNoCategorie());
}
if (critere.getTitle() != null && !critere.getTitle().isEmpty()) {
sql.append(" AND a.nom_article LIKE ?");
params.add('%' + critere.getTitle() + '%');
}
if (critere.getVenteOptions() != null && critere.getVenteOptions().length > 0) {
sql.append(" AND (");
boolean isFirstCondition = true;
for (String option : critere.getVenteOptions()) {
if (option.equals("venteEnCours")) {
if (!isFirstCondition) {
sql.append(" OR ");
}
sql.append(" (a.date_debut_encheres <= NOW() AND a.date_fin_encheres >= NOW()) ");
isFirstCondition = false;
}
if (option.equals("ventesNonDebutees")) {
if (!isFirstCondition) {
sql.append(" OR ");
}
sql.append(" (a.date_debut_encheres > NOW()) ");
isFirstCondition = false;
}
if (option.equals("ventesTerminees")) {
if (!isFirstCondition) {
sql.append(" OR ");
}
sql.append(" (a.date_fin_encheres < NOW()) ");
isFirstCondition = false;
}
}
sql.append(") AND a.no_utilisateur = ?");
params.add(critere.getNoVendeur());
}
if (critere.getAchatOptions() != null && critere.getAchatOptions().length > 0) {
sql.append(" AND (");
boolean isFirstCondition = true;
for (String option : critere.getAchatOptions()) {
if (option.equals("encheresOuvertes")) {
if (!isFirstCondition) {
sql.append(" OR ");
}
sql.append(" (a.date_debut_encheres <= NOW() AND a.date_fin_encheres >= NOW()) ");
isFirstCondition = false;
}
if (option.equals("enchereEnCours")) {
if (!isFirstCondition) {
sql.append(" OR ");
}
sql.append(" (e.no_utilisateur = ? AND a.date_debut_encheres <= NOW() AND a.date_fin_encheres >= NOW()) ");
isFirstCondition = false;
params.add(critere.getNoVendeur());
}
if (option.equals("enchereRemportees")) {
if (!isFirstCondition) {
sql.append(" OR ");
}
sql.append(" (e.no_utilisateur = ? AND e.montant_enchere = (SELECT MAX(montant_enchere) FROM ENCHERES WHERE no_article = a.no_article)) ");
isFirstCondition = false;
params.add(critere.getNoVendeur());
}
}
sql.append(")");
}
// Exécute la requête paginée
int articles = jdbcTemplate.queryForObject(sql.toString(), Integer.class, params.toArray());
// Retourne le nombre d'articles
return articles; return articles;
} }

View File

@@ -12,6 +12,10 @@ home.sell.finish = My completed sales
home.button.search = Search home.button.search = Search
home.button.lang = Passer en fran\u00E7ais home.button.lang = Passer en fran\u00E7ais
home.button.lang2 = EN home.button.lang2 = EN
home.button.first = First
home.button.end = End
home.button.next = Next
home.button.previous = Previous
home.credit = My credits: home.credit = My credits:
home.nav.enchere = Auctions home.nav.enchere = Auctions
home.nav.vend = Sell an item home.nav.vend = Sell an item

View File

@@ -12,6 +12,10 @@ home.sell.finish = Mes ventes termin\u00E9es
home.button.search = Recherche home.button.search = Recherche
home.button.lang = Switch to English home.button.lang = Switch to English
home.button.lang2 = FR home.button.lang2 = FR
home.button.first = D\u00e9but
home.button.end = Fin
home.button.next = Suivant
home.button.previous = Pr\u00e9cedent
home.credit = Mes cr\u00e9dits : home.credit = Mes cr\u00e9dits :
home.nav.enchere = Encheres home.nav.enchere = Encheres
home.nav.vend = Vendre un article home.nav.vend = Vendre un article
@@ -20,7 +24,7 @@ home.profil.profil = Profil
home.profil.logout = D\u00e9connexion home.profil.logout = D\u00e9connexion
home.article.sellprice = Prix de vente: home.article.sellprice = Prix de vente:
home.article.seller = Vendeur: home.article.seller = Vendeur:
home.article.end = Fin de l'ench\u00E8re: home.article.end = Fin de l'ench\u00E8re:
footer.desc = Cr\u00e9\u00e9e par l'association "Les objets sont nos amis", ENI-Ench\u00e9res a pour objectif d'aider ses membres \u00E0 vendre ou acheter des objets de tout genre. footer.desc = Cr\u00e9\u00e9e par l'association "Les objets sont nos amis", ENI-Ench\u00e9res a pour objectif d'aider ses membres \u00E0 vendre ou acheter des objets de tout genre.
footer. footer.
@@ -116,4 +120,5 @@ article.details.address.unknown = Adresse inconnue
article.details.validation.amount.required = Le montant de l'ench\u00E8re est requis. article.details.validation.amount.required = Le montant de l'ench\u00E8re est requis.
edit.article.title = Modifier mon article edit.article.title = Modifier mon article

View File

@@ -143,6 +143,42 @@
</a> </a>
</div> </div>
</div> </div>
<!-- Pagination -->
<div class="row mt-4">
<div class="col-md-12">
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" th:href="@{/(page=0)}">
<span aria-hidden="true">&laquo;</span>
<span th:text="#{home.button.first}"></span>
</a>
</li>
<li th:if="${currentPage > 0}" class="page-item">
<a class="page-link" th:href="@{/(page=${currentPage - 1})}">
<span aria-hidden="true">&lsaquo;</span>
<span th:text="#{home.button.previous}"></span>
</a>
</li>
<li th:if="${currentPage < totalPages - 1}" class="page-item">
<a class="page-link" th:href="@{/(page=${currentPage + 1})}">
<span aria-hidden="true">&rsaquo;</span>
<span th:text="#{home.button.next}"></span>
</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{/(page=${totalPages - 1})}">
<span aria-hidden="true">&raquo;</span>
<span th:text="#{home.button.end}"></span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div> </div>
</div> </div>
</body> </body>

View File

@@ -5,57 +5,54 @@
<link rel="stylesheet" type="text/css" href="style.css"> <!-- Ajoutez le lien vers votre fichier CSS si nécessaire --> <link rel="stylesheet" type="text/css" href="style.css"> <!-- Ajoutez le lien vers votre fichier CSS si nécessaire -->
</head> </head>
<body> <body>
<div id="container-main"> <div id="container-main" class="container mt-4">
<h2 th:text="#{admin.categories.title}">Liste des catégories modifiées</h2> <h2 th:text="#{admin.categories.title}" class="mb-4">Liste des catégories modifiées</h2>
<table> <div class="table-responsive">
<thead> <table class="table table-bordered">
<tr> <thead class="thead-dark">
<th th:text="#{admin.categories.table.name}">Nom</th> <tr>
<th th:text="#{admin.categories.table.action}">Action</th> <th th:text="#{admin.categories.table.name}">Nom</th>
</tr> <th th:text="#{admin.categories.table.action}">Action</th>
</thead> </tr>
<tbody> </thead>
<tr th:each="categorie : ${categories}"> <tbody>
<td> <tr th:each="categorie : ${categories}">
<form th:action="@{/admin/update}" method="post"> <td>
<input type="text" name="newCategorie" id="newCategorie" th:value="${categorie.libelle}"> <form th:action="@{/admin/update}" method="post">
<input type="hidden" name="IdCategorie" id="IdCategorie" th:value="${categorie.id}"> <input type="text" name="newCategorie" id="newCategorie" th:value="${categorie.libelle}" class="form-control">
<button th:text="#{admin.categories.table.save}">Sauvegarder</button> <input type="hidden" name="IdCategorie" id="IdCategorie" th:value="${categorie.id}">
</form> <button type="submit" th:text="#{admin.categories.table.save}" class="btn btn-primary mt-2"></button>
</td> </form>
<td> </td>
<form th:action="@{/admin/catDelete}" method="post"> <td>
<input type="hidden" name="deleteIdCategorie" id="deleteIdCategorie" th:value="${categorie.id}"> <form th:action="@{/admin/deleteC}" method="post">
<button th:text="#{admin.categories.table.delete}">Supprimer</button> <input type="hidden" name="deleteIdCategorie" id="deleteIdCategorie" th:value="${categorie.id}">
</form> <button type="submit" th:text="#{admin.categories.table.delete}" class="btn btn-danger mt-2"></button>
</td> </form>
</tr> </td>
</tbody> </tr>
</table> </tbody>
<form th:action="@{/admin/new}" th:object="${categorie}" method="post"> </table>
<input type="text" th:field="*{libelle}" id="nom"> </div>
<button th:text="#{admin.categories.table.add}">Ajouter</button> <form th:action="@{/admin/new}" th:object="${categorie}" method="post" class="mb-4">
<input type="text" th:field="*{libelle}" id="nom" class="form-control">
<button type="submit" th:text="#{admin.categories.table.add}" class="btn btn-success mt-2"></button>
</form> </form>
<h2 th:text="#{admin.users.title}">Liste des utilisateurs</h2>
<table> <h2 th:text="#{admin.users.title}" class="mb-4">Liste des utilisateurs</h2>
<thead> <div class="table-responsive">
<tr> <table class="table table-bordered">
<th th:text="#{admin.users.table.id}">ID</th> <thead class="thead-dark">
<th th:text="#{admin.users.table.username}">Pseudo</th> <tr>
<th th:text="#{admin.users.table.lastname}">Nom</th> <th th:text="#{admin.users.table.id}">ID</th>
<th th:text="#{admin.users.table.firstname}">Prénom</th> <th th:text="#{admin.users.table.username}">Pseudo</th>
<th th:text="#{admin.users.table.email}">Email</th> <th th:text="#{admin.users.table.lastname}">Nom</th>
<th th:text="#{admin.users.table.action}">Action</th> <th th:text="#{admin.users.table.firstname}">Prénom</th>
</tr> <th th:text="#{admin.users.table.email}">Email</th>
</thead> <th th:text="#{admin.categories.table.action}">Action</th>
<tbody> </tr>
<tr th:each="user : ${userProfil}"> </thead>
<td th:text="${user.id}"></td> <tbody>
<td th:text="${user.pseudo}"></td>
<td th:text="${user.nom}"></td>
<td th:text="${user.prenom}"></td>
<td th:text="${user.email}"></td>
</tr>
<tr th:each="user : ${userProfil}"> <tr th:each="user : ${userProfil}">
<td th:text="${user.id}"></td> <td th:text="${user.id}"></td>
<td th:text="${user.pseudo}"></td> <td th:text="${user.pseudo}"></td>
@@ -64,31 +61,31 @@
<td th:text="${user.email}"></td> <td th:text="${user.email}"></td>
<td> <td>
<form th:action="@{/admin/update/credit}" method="post"> <form th:action="@{/admin/update/credit}" method="post">
<input type="number" name="newCredit" id="newCredit" th:value="${user.credit}"> <input type="number" name="newCredit" id="newCredit" th:value="${user.credit}" class="form-control">
<input type="hidden" name="idUser" id="idUser" th:value="${user.id}"> <input type="hidden" name="idUser" id="idUser" th:value="${user.id}">
<button>Sauvegarder</button> <button type="submit" class="btn btn-primary mt-2">Sauvegarder</button>
</form> </form>
</td> </td>
<td> <td>
<form th:action="@{/admin/disabled}" method="post"> <form th:action="@{/admin/disabled}" method="post">
<input type="hidden" name="userDisabled" id="userDisabled" th:value="${user.id}"> <input type="hidden" name="userDisabled" id="userDisabled" th:value="${user.id}">
<input type="hidden" name="isDis" id="isDis" th:value="${user.isDisabled}"> <input type="hidden" name="isDis" id="isDis" th:value="${user.isDisabled}">
<button th:text="${user.isDisabled} ? 'Activer' : 'Désactiver'"></button> <button type="submit" th:text="${user.isDisabled} ? 'Activer' : 'Désactiver'" class="btn btn-secondary mt-2"></button>
</form> </form>
<form th:action="@{/admin/delete}" method="post"> <form th:action="@{/admin/delete}" method="post">
<input type="hidden" name="userDelete" id="userDelete" th:value="${user.id}"> <input type="hidden" name="userDelete" id="userDelete" th:value="${user.id}">
<button>Supprimer</button> <button type="submit" class="btn btn-danger mt-2">Supprimer</button>
</form> </form>
<form th:action="@{/admin/administrateur}" method="post"> <form th:action="@{/admin/administrateur}" method="post">
<input type="hidden" name="userAdmin" id="userAdmin" th:value="${user.id}"> <input type="hidden" name="userAdmin" id="userAdmin" th:value="${user.id}">
<input type="hidden" name="isAdmin" id="isAdmin" th:value="${user.admin}"> <input type="hidden" name="isAdmin" id="isAdmin" th:value="${user.admin}">
<button th:text="${user.admin} ? 'Retirer admin' : 'Ajouter admin'"></button> <button type="submit" th:text="${user.admin} ? 'Retirer admin' : 'Ajouter admin'" class="btn btn-info mt-2"></button>
</form> </form>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -45,6 +45,7 @@
</a> </a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
<a class="dropdown-item" href="/profil" th:text="#{home.profil.profil}"></a> <!-- Bouton de profil --> <a class="dropdown-item" href="/profil" th:text="#{home.profil.profil}"></a> <!-- Bouton de profil -->
<a class="dropdown-item" th:if="${#authorization.expression('hasRole(''ADMIN'')')}" href="/admin"> Administration </a>
<a class="dropdown-item" href="/logout" th:text="#{home.profil.logout}"></a> <!-- Bouton de déconnexion --> <a class="dropdown-item" href="/logout" th:text="#{home.profil.logout}"></a> <!-- Bouton de déconnexion -->
<div class="dropdown-divider"></div> <!-- Diviseur --> <div class="dropdown-divider"></div> <!-- Diviseur -->
<a class="dropdown-item" th:href="@{/change-language}" th:text="#{home.button.lang}"></a> <!-- Option de changement de langue vers l'anglais --><!-- Option de changement de langue vers le français --> <a class="dropdown-item" th:href="@{/change-language}" th:text="#{home.button.lang}"></a> <!-- Option de changement de langue vers l'anglais --><!-- Option de changement de langue vers le français -->