diff --git a/MCD.JPG b/MCD.JPG new file mode 100644 index 0000000000000000000000000000000000000000..499a22166dc5f09eaeedf1d9105a2ce28b494cb7 Binary files /dev/null and b/MCD.JPG differ diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/config/Config.java b/src/main/java/fr/but/infoetu/MeetingPlannr/config/Config.java index f67aab4b4f140198c1e5e34e5249e5ffc7efc5ae..0612dec96430fd5b61c8c35a016776c2a44f4602 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/config/Config.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/config/Config.java @@ -6,11 +6,11 @@ import java.util.Arrays; public class Config { public static final int MAX_GENERATIONS = 1; - public static final ArrayList<String> WEEK_DAYS = new ArrayList<>(Arrays.asList("MONDAY", "WEDNESDAY", "THURSDAY", "FRIDAY")); - public static final int SLOT_DURATION = 11; + public static final ArrayList<String> WEEK_DAYS = new ArrayList<>(Arrays.asList("WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY")); + public static final int SLOT_DURATION = 60; public static final double DAY_START = 8; public static final double DAY_END = 17; - public static final int MAX_PEOPLE = 3000; + public static final int MAX_PEOPLE = 30; public static ArrayList<Time> createTimeSlots() { ArrayList<Time> timeSlots = new ArrayList<>(); diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/controller/AdminController.java b/src/main/java/fr/but/infoetu/MeetingPlannr/controller/AdminController.java index 091a81e5592b01811e6fcb698f2d2f34092eaae8..e0309e4dbba6ffd14c1542e7cc09f4405706dd8c 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/controller/AdminController.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/controller/AdminController.java @@ -58,10 +58,6 @@ public class AdminController { @RequestMapping(value = "admin/dashboard", method = RequestMethod.GET) private String adminDashboard(@AuthenticationPrincipal UserDetails details, Model model) { - if (details == null) { - return "redirect:/public/login"; - } - User user = ur.findByUsername(details.getUsername()).get(); model.addAttribute("currentUser", user); @@ -73,12 +69,9 @@ public class AdminController { @RequestParam(defaultValue = "1") int page, @RequestParam(required = false) String search, Model model) { - if (details == null || !details.getAuthorities().stream() - .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) { - return "redirect:/public/login"; - } + - int pageSize = 10; + int pageSize = 5; List<User> allUsers = search != null && !search.trim().isEmpty() ? ur.findByNameContainingOrSurnameContainingOrUsernameContaining(search, search, search) : ur.findAll(); @@ -105,17 +98,20 @@ public class AdminController { private String requestsList(@AuthenticationPrincipal UserDetails details, @RequestParam(defaultValue = "1") int page, @RequestParam(required = false) String search, + @RequestParam(required = false) Boolean filter, Model model) { - if (details == null || !details.getAuthorities().stream() - .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) { - return "redirect:/public/login"; - } - int pageSize = 10; + int pageSize = 5; List<Request> allRequests = search != null && !search.trim().isEmpty() ? rr.findByReasonContainingOrDescriptionContaining(search, search) : rr.findAll(); + if (filter != null && filter) { + allRequests = allRequests.stream() + .filter(req -> !rs.hasMeeting(req)) + .collect(Collectors.toList()); + } + int start = (page - 1) * pageSize; int end = Math.min(start + pageSize, allRequests.size()); List<Request> requests = allRequests.subList(start, end); @@ -128,29 +124,40 @@ public class AdminController { model.addAttribute("currentPage", page); model.addAttribute("totalPages", (int) Math.ceil(allRequests.size() / (double) pageSize)); model.addAttribute("currentUser", ur.findByUsername(details.getUsername()).get()); + model.addAttribute("filter", filter); return "admin/requests"; } @RequestMapping(value = "admin/requests/validate/{rno}", method = RequestMethod.POST) - private String validateRequest(@PathVariable int rno, @AuthenticationPrincipal UserDetails details) { - if (details == null || !details.getAuthorities().stream() - .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) { - return "redirect:/public/login"; - } + private String validateRequest(@PathVariable int rno, @AuthenticationPrincipal UserDetails details, + @RequestParam(defaultValue = "1") int page, + @RequestParam(required = false) String search, + @RequestParam(required = false) Boolean filter) { + Optional<Request> requestOpt = rr.findById(rno); if (requestOpt.isPresent()) { Request request = requestOpt.get(); - + User user = request.getUser(); Meeting meeting = new Meeting(); meeting.setRequest(request); - meeting.setUser(request.getUser()); - + meeting.setUser(user); + if(user.getUsername().endsWith("@univ-lille.fr")) { + us.sendValidationMeeting(user, meeting); + } mr.save(meeting); } - return "redirect:/admin/requests"; + String redirectUrl = String.format("redirect:/admin/requests?page=%d", page); + if (search != null) { + redirectUrl += "&search=" + search; + } + if (filter != null) { + redirectUrl += "&filter=" + filter; + } + + return redirectUrl; } @RequestMapping(value = "admin/meetings", method = RequestMethod.GET) @@ -163,7 +170,7 @@ public class AdminController { return "redirect:/public/login"; } - int pageSize = 10; + int pageSize = 5; List<Meeting> allMeetings = search != null && !search.trim().isEmpty() ? mr.findByRequestReasonContainingOrRequestDescriptionContaining(search, search) : mr.findAll(); diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/controller/PublicController.java b/src/main/java/fr/but/infoetu/MeetingPlannr/controller/PublicController.java index f24108f88228e588782f82c8c5d6e7891079f841..ba5859f5c97c90b3b325401b0f315e654498bcca 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/controller/PublicController.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/controller/PublicController.java @@ -5,11 +5,15 @@ import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; import fr.but.infoetu.meetingplannr.pojo.User; import fr.but.infoetu.meetingplannr.repository.UserRepository; @@ -17,6 +21,7 @@ import fr.but.infoetu.meetingplannr.service.UserService; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; +import java.time.LocalDateTime; import java.util.Optional; @Controller @@ -28,6 +33,12 @@ public class PublicController { @Autowired private UserService userService; + @Autowired + private JavaMailSender mailSender; + + @Autowired + private PasswordEncoder passwordEncoder; + @RequestMapping(value = "public/login", method = RequestMethod.GET) public String loginForm() { if (isAuthenticated()) { @@ -66,6 +77,60 @@ public class PublicController { return "redirect:/user/listeAction"; } + @RequestMapping(value = "public/password_change", method = RequestMethod.GET) + public String passwordChangeForm() { + return "public/passwordChange"; + } + + @RequestMapping(value = "public/perform_password_change", method = RequestMethod.POST) + public String performPasswordChange(@RequestParam("email") String email, @RequestParam("newPassword") String newPassword, Model model) { + Optional<User> userOpt = ur.findByUsername(email); + if (!userOpt.isPresent()) { + model.addAttribute("errorMessage", "Email not found."); + return "public/passwordChange"; + } + + User user = userOpt.get(); + + if(!user.getUsername().endsWith("@univ-lille.fr")) { + model.addAttribute("errorMessage", "Le mot de passe ne peut pas être modifié car l'adresse email n'appartient pas à l'université de Lille"); + return "public/login"; + } + userService.sendPasswordVerificationEmail(user, newPassword); + + model.addAttribute("successMessage", "Un email de vérification a été envoyé à " + email + "."); + return "public/passwordChange"; + } + + + @RequestMapping(value = "public/verify_password_change", method = RequestMethod.GET) + public String verifyPasswordChange(@RequestParam("token") String token, @RequestParam("password") String password, Model model) { + Optional<User> userOpt = ur.findByVerificationToken(token); + + if (!userOpt.isPresent()) { + model.addAttribute("errorMessage", "Token invalide ou expiré."); + return "public/passwordChange"; + } + + User user = userOpt.get(); + + if (user.getTokenExpiration().isBefore(LocalDateTime.now())) { + model.addAttribute("errorMessage", "Le token a expiré."); + return "public/passwordChange"; + } + + System.out.println("Password: " + password); + user.setPassword(passwordEncoder.encode(password)); + user.setVerificationToken(null); + user.setTokenExpiration(null); + ur.save(user); + + return "redirect:/public/login"; + } + + + + private boolean isAuthenticated() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return authentication != null && diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/controller/UserController.java b/src/main/java/fr/but/infoetu/MeetingPlannr/controller/UserController.java index a08a9378298d7125c267ad8ddaaa12d633745e44..5ad54b0969eace0bb8e521d458592316f85b385b 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/controller/UserController.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/controller/UserController.java @@ -3,7 +3,9 @@ package fr.but.infoetu.meetingplannr.controller; import java.time.LocalDate; import java.time.LocalTime; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -214,12 +216,40 @@ public class UserController { } @RequestMapping(value = "user/calendar", method = RequestMethod.GET) - private String calendar(@AuthenticationPrincipal UserDetails details, Model model) { + private String calendar(@AuthenticationPrincipal UserDetails details, Model model, HttpServletRequest request) { if (details == null) { return "redirect:/public/login"; } User user = ur.findByUsername(details.getUsername()).get(); model.addAttribute("currentUser", user); + + String monthParam = request.getParameter("month"); + String yearParam = request.getParameter("year"); + LocalDate today = LocalDate.now(); + LocalDate currentDate; + + if (monthParam != null && yearParam != null) { + currentDate = LocalDate.of(Integer.parseInt(yearParam), Integer.parseInt(monthParam), 1); + } else { + currentDate = LocalDate.now().withDayOfMonth(1); + } + + LocalDate date = currentDate; + int month = date.getMonthValue(); + Map<LocalDate, List<Meeting>> meetingsByDate = new HashMap<>(); + + while (date.getMonthValue() == month) { + List<Meeting> meetingsForDay = mr.findByRequestDate(date); + meetingsByDate.put(date, meetingsForDay); + date = date.plusDays(1); + } + + model.addAttribute("meetingsByDate", meetingsByDate); + model.addAttribute("currentDate", currentDate); + model.addAttribute("previousMonth", currentDate.minusMonths(1)); + model.addAttribute("nextMonth", currentDate.plusMonths(1)); + model.addAttribute("today", today); + return "user/calendar"; } diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/User.java b/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/User.java index 8c621f45cb0b4f15e41c033691038aab8e2de8f0..403f8fade06b4b0c86228842fe0c966a005725c8 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/User.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/pojo/User.java @@ -1,6 +1,7 @@ package fr.but.infoetu.meetingplannr.pojo; import java.time.LocalDate; +import java.time.LocalDateTime; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -36,6 +37,8 @@ import java.util.Collections; @Table(name = "users") public class User implements UserDetails { private static final String REQUIRED = "est obligatoire"; + private String verificationToken; + private LocalDateTime tokenExpiration; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_uno_seq") diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/repository/UserRepository.java b/src/main/java/fr/but/infoetu/MeetingPlannr/repository/UserRepository.java index f3329fbcb752c488932df28b079c1e8f5ed95ef4..7aa3e46cdbeea46fbe163319aa89da538bd76081 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/repository/UserRepository.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/repository/UserRepository.java @@ -13,4 +13,6 @@ public interface UserRepository extends JpaRepository<User, Integer>{ List<User> findByNameContainingOrSurnameContainingOrUsernameContaining(String search, String search2, String search3); + + Optional<User> findByVerificationToken(String token); } diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/service/RequestService.java b/src/main/java/fr/but/infoetu/MeetingPlannr/service/RequestService.java index 52539c4fb40c8b8134eb0d96aa36fd7da29ba6b7..a98d42db6fbd923ae2f17ba7a281945f2a84494e 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/service/RequestService.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/service/RequestService.java @@ -20,7 +20,6 @@ public class RequestService { } public boolean hasMeeting(Request request){ - System.err.println(mr.existsByRequest(request)); return mr.existsByRequest(request); } } diff --git a/src/main/java/fr/but/infoetu/MeetingPlannr/service/UserService.java b/src/main/java/fr/but/infoetu/MeetingPlannr/service/UserService.java index cc613c90f4e2784971b121a5911c894243d0af40..f847c54374dfce50e1be7e449df4ce2583b1c35c 100644 --- a/src/main/java/fr/but/infoetu/MeetingPlannr/service/UserService.java +++ b/src/main/java/fr/but/infoetu/MeetingPlannr/service/UserService.java @@ -1,16 +1,21 @@ package fr.but.infoetu.meetingplannr.service; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.LocalDateTime; import java.util.UUID; +import fr.but.infoetu.meetingplannr.pojo.Meeting; import fr.but.infoetu.meetingplannr.pojo.User; import fr.but.infoetu.meetingplannr.repository.UserRepository; +import jakarta.mail.internet.MimeMessage; @Service public class UserService { @@ -18,6 +23,8 @@ public class UserService { private PasswordEncoder passwordEncoder; @Autowired private UserRepository userRepository; + @Autowired + JavaMailSender sender; private final String UPLOAD_DIR = "src/main/resources/static/uploads/"; @@ -30,6 +37,25 @@ public class UserService { public void banUser(Integer uno) { User user = userRepository.findById(uno) .orElseThrow(() -> new RuntimeException("Utilisateur non trouvé")); + + if (user.getUsername().endsWith("@univ-lille.fr")) { + MimeMessage message = sender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message); + + try { + helper.setFrom("paul.cancel.etu@univ-lille.fr"); + helper.setTo(user.getUsername()); + helper.setSubject("Compte banni"); + helper.setText("Votre compte : " + user.getUsername() + " a été banni le " + java.time.LocalDate.now()); + sender.send(message); + } catch (jakarta.mail.MessagingException e) { + throw new RuntimeException("Erreur lors de l'envoi de l'email", e); + } + } + + if ("ROLE_ADMIN".equals(user.getAuthority())) { + throw new RuntimeException("Impossible de bannir un administrateur"); + } user.setEnabled(false); userRepository.save(user); } @@ -43,4 +69,51 @@ public class UserService { return fileName; } + + public void sendPasswordVerificationEmail(User user, String newPassword) { + String token = UUID.randomUUID().toString(); + user.setVerificationToken(token); + user.setTokenExpiration(LocalDateTime.now().plusHours(1)); + userRepository.save(user); + + String verificationUrl = "http://localhost:8080/meetingplannr/public/verify_password_change?token=" + token + "&password=" + newPassword; + String emailContent = "Bonjour,\n\nCliquez sur le lien suivant pour confirmer votre changement de mot de passe :\n" + + verificationUrl; + + MimeMessage message = sender.createMimeMessage(); + try { + MimeMessageHelper helper = new MimeMessageHelper(message); + helper.setFrom("paul.cancel.etu@univ-lille.fr"); + helper.setTo(user.getUsername()); + helper.setSubject("Vérification du changement de mot de passe"); + helper.setText(emailContent); + sender.send(message); + } catch (jakarta.mail.MessagingException e) { + throw new RuntimeException("Erreur lors de l'envoi de l'email", e); + } + } + + public void sendValidationMeeting(User user, Meeting meeting){ + String emailContent = "Bonjour " + user.getName() + " ,\n\nVotre demande de rendez-vous a été validée.\n\n" + + "Date : " + meeting.getRequest().getDate() + "\n" + + "Heure : " + meeting.getRequest().getTime() + "\n" + + "Nombre de personnes : " + meeting.getRequest().getNumberOfPeople() + "\n" + + "Raison : " + meeting.getRequest().getReason() + "\n" + + "Description : " + meeting.getRequest().getDescription() + "\n\n" + + "Cordialement,\n\n" + + "L'équipe MeetingPlannr"; + + MimeMessage message = sender.createMimeMessage(); + try { + MimeMessageHelper helper = new MimeMessageHelper(message); + helper.setFrom("paul.cancel.etu@univ-lille.fr"); + helper.setTo(user.getUsername()); + helper.setSubject("Rendez-vous validé"); + helper.setText(emailContent); + sender.send(message); + } catch (jakarta.mail.MessagingException e) { + throw new RuntimeException("Erreur lors de l'envoi de l'email", e); + } + } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 03e0917137621c545da243c365eb5200a552d0eb..e884b3e0e0b996d0ae11129bbdb7724154c12f42 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,8 +26,12 @@ logging.level.org.springframework.jdbc=TRACE spring.security.filter.order=10 server.servlet.session.timeout=30m +logging.level.org.springframework.mail=DEBUG + spring.mail.host=smtp.univ-lille.fr spring.mail.port=587 -spring.mail.username=paul.cancel.etu -spring.mail.password=meetingplannr +spring.mail.username=paul.cancel.etu@univ-lille.fr +# mettre : export MAIL_PASSWORD="password" pour linux ou $env:MAIL_PASSWORD="password" +spring.mail.password=${MAIL_PASSWORD} + spring.mail.properties.mail.smtp.starttls.enable=true \ No newline at end of file diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql index 3937dba88971e8d8d6975cd4af9867c2153b8b01..445c81f25040830b76b0092e85c09ca42de5368c 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/import.sql @@ -2,7 +2,7 @@ DROP SEQUENCE IF EXISTS users_uno_seq; CREATE SEQUENCE users_uno_seq START WITH 1; -- password: password123 -INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'john.doe@example.com', 'John', 'Doe', '0612345678', '1990-05-15', '$2a$12$o0C1lgpgzoxPrE64DHda6O0DEDqQznVxqXb5y6gzWne3BP4nZMWrC', 'ROLE_USER', true); +INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'paul.cancel.etu@univ-lille.fr', 'John', 'Doe', '0612345678', '1990-05-15', '$2a$12$o0C1lgpgzoxPrE64DHda6O0DEDqQznVxqXb5y6gzWne3BP4nZMWrC', 'ROLE_USER', true); -- password: securepwd INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'jane.smith@example.com', 'Jane', 'Smith', '0698765432', '1985-08-22', '$2a$12$9DmwZZv31epkRx6kUOCeueYRCrlAUyV1J0iB6eienUWRKT3ozpLGu', 'ROLE_USER', true); @@ -22,15 +22,51 @@ INSERT INTO users (uno, username, name, surname, phone_number, birthdate, passwo -- password: noemiegoat INSERT INTO users (uno, username, name, surname, phone_number, birthdate, password, authority, enabled) VALUES (nextval('users_uno_seq'), 'theo.vienne@net.fr', 'Theo', 'Vienne', '0619515793', '2000-08-31', '$2a$12$XWqr1MGt9oSxRm2YfEMQle4QWq9r9QcpSXDcOgsEzEQTqtLRdBgcC', 'ROLE_ADMIN', true); --- Modifications des INSERT de requests pour inclure l'heure -INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Rendez-vous pour discussion projet', 'Discussion des objectifs du projet', 6, '09:00', '2024-01-15', 3); -INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Point d''avancement mensuel', 'Revue mensuelle des progrès', 6, '14:30', '2024-02-20', 5); -INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de planification', 'Planification des prochaines étapes', 6, '10:00', '2024-03-10', 4); -INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Bilan trimestriel', 'Evaluation des résultats du trimestre', 6, '15:00', '2024-04-05', 6); -INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Session de développement', 'Session de codage en équipe', 6, '11:30', '2024-05-18', 2); - -INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Rendez-vous pour discussion projet')); -INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Point d''avancement mensuel')); -INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Réunion de planification')); -INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Bilan trimestriel')); -INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Session de développement')); \ No newline at end of file + +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Rendez-vous pour discussion projet', 'Discussion des objectifs du projet', 6, '09:00', '2025-01-25', 3); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Point d''avancement mensuel', 'Revue mensuelle des progrès', 6, '14:00', '2025-01-24', 5); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de planification', 'Planification des prochaines étapes', 6, '10:00', '2025-01-25', 4); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Bilan trimestriel', 'Evaluation des résultats du trimestre', 6, '15:00', '2025-01-29', 6); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Session de développement', 'Session de codage en équipe', 6, '11:00', '2025-01-24', 2); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de brainstorming', 'Session de brainstorming pour nouvelles idées', 3, '09:00', '2025-01-25', 4); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Atelier de formation', 'Formation sur les nouvelles technologies', 4, '16:00', '2025-01-25', 5); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de coordination', 'Coordination des équipes de projet', 2, '11:00', '2025-01-25', 3); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Déjeuner d équipe', 'Déjeuner pour renforcer l esprit d équipe', 7, '12:00', '2025-01-25', 6); + +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de lancement', 'Lancement du nouveau projet', 2, '09:00', '2025-01-23', 5); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de suivi', 'Suivi des tâches en cours', 3, '10:00', '2025-01-23', 4); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de clôture', 'Clôture du projet en cours', 4, '11:00', '2025-01-23', 6); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de feedback', 'Retour d''expérience sur le projet', 5, '14:00', '2025-01-23', 3); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de coordination', 'Coordination des équipes', 6, '15:00', '2025-01-23', 4); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion', 'Planification des tâches', 7, '16:00', '2025-01-23', 5); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de brainstorming', 'Brainstorming pour nouvelles idées', 2, '09:00', '2025-01-26', 4); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de formation', 'Formation sur les nouvelles technologies', 3, '10:00', '2025-01-26', 6); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de développement', 'Développement de nouvelles fonctionnalités', 4, '11:00', '2025-01-26', 3); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de revue', 'Revue des progrès', 5, '14:00', '2025-01-26', 5); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de stratégie', 'Stratégie pour le prochain trimestre', 1, '15:00', '2025-01-26', 4); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de bilan', 'Bilan des résultats', 1, '16:00', '2025-01-26', 6); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de lancement', 'Lancement du nouveau produit', 2, '09:00', '2025-01-27', 5); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de suivi', 'Suivi des ventes', 3, '10:00', '2025-01-27', 4); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de clôture', 'Clôture du trimestre', 4, '11:00', '2025-01-27', 6); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de feedback', 'Retour d''expérience des clients', 5, '14:00', '2025-01-27', 3); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de coordination', 'Coordination des équipes de vente', 6, '15:00', '2025-01-27', 4); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de planification', 'Planification des ventes', 7, '16:00', '2025-01-27', 5); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de brainstorming', 'Brainstorming pour nouvelles stratégies', 2, '09:00', '2025-01-28', 4); +INSERT INTO requests (reason, description, uno, time, date, number_of_people) VALUES ('Réunion de formation', 'Formation sur les techniques de vente', 3, '10:00', '2025-01-28', 6); + +INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Rendez-vous pour discussion projet' AND date = '2025-01-25')); +INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Point d''avancement mensuel' AND date = '2025-01-24')); +INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Réunion de planification' AND date = '2025-01-25')); +INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Bilan trimestriel' AND date = '2025-01-29')); +INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Session de développement' AND date = '2025-01-24')); + +INSERT INTO meetings (uno, rno) VALUES (2, (SELECT rno FROM requests WHERE reason = 'Réunion de lancement' AND date = '2025-01-23')); +INSERT INTO meetings (uno, rno) VALUES (3, (SELECT rno FROM requests WHERE reason = 'Réunion de suivi' AND date = '2025-01-23')); +INSERT INTO meetings (uno, rno) VALUES (4, (SELECT rno FROM requests WHERE reason = 'Réunion de clôture' AND date = '2025-01-23')); +INSERT INTO meetings (uno, rno) VALUES (5, (SELECT rno FROM requests WHERE reason = 'Réunion de feedback' AND date = '2025-01-23')); +INSERT INTO meetings (uno, rno) VALUES (6, (SELECT rno FROM requests WHERE reason = 'Réunion de coordination' AND date = '2025-01-23')); +INSERT INTO meetings (uno, rno) VALUES (7, (SELECT rno FROM requests WHERE reason = 'Réunion' AND date = '2025-01-23')); +INSERT INTO meetings (uno, rno) VALUES (2, (SELECT rno FROM requests WHERE reason = 'Réunion de brainstorming' AND date = '2025-01-26')); +INSERT INTO meetings (uno, rno) VALUES (3, (SELECT rno FROM requests WHERE reason = 'Réunion de formation' AND date = '2025-01-26')); +INSERT INTO meetings (uno, rno) VALUES (4, (SELECT rno FROM requests WHERE reason = 'Réunion de développement' AND date = '2025-01-26')); +INSERT INTO meetings (uno, rno) VALUES (5, (SELECT rno FROM requests WHERE reason = 'Réunion de revue' AND date = '2025-01-26')); \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 73b8482fc90555712eb2d082367adfcc4b15aa40..f2f57a16d37aed8f60ccb8a87338fb99d1e8847d 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1,4 +1,4 @@ -header.title=Meeting Planner +header.title=MeetingPlannr header.subtitle=Organize your meetings easily footer.copyright=© 2023 Meeting Planner. All rights reserved. @@ -48,6 +48,7 @@ meeting.new.description=Description meeting.new.numberOfPeople=Number of People meeting.new.submit=Submit meeting.new.return=Return to Calendar +meeting.new.occupied=Occupied actions.page.title=Actions actions.greeting=Hello @@ -110,6 +111,8 @@ admin.requests.validated=Validated admin.requests.validate=Validate admin.requests.no.found=No requests found admin.requests.return=Return to Dashboard +admin.requests.filter.button=Filter +admin.requests.filter.active=Filtered admin.meetings.page.title=Meeting Management admin.meetings.title=Manage Meetings admin.meetings.search.placeholder=Search meetings... @@ -130,3 +133,9 @@ admin.dashboard.users=Manage Users admin.dashboard.requests=Manage Requests admin.dashboard.meetings=Manage Meetings admin.dashboard.return=Return to User Actions + +password.change.title=Forgot Password +password.change.email=Email +password.change.submit=Submit +password.change.error=Error processing your request. Please try again. +password.change.newPassword=New Password diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties index 73b8482fc90555712eb2d082367adfcc4b15aa40..f2f57a16d37aed8f60ccb8a87338fb99d1e8847d 100644 --- a/src/main/resources/messages_en.properties +++ b/src/main/resources/messages_en.properties @@ -1,4 +1,4 @@ -header.title=Meeting Planner +header.title=MeetingPlannr header.subtitle=Organize your meetings easily footer.copyright=© 2023 Meeting Planner. All rights reserved. @@ -48,6 +48,7 @@ meeting.new.description=Description meeting.new.numberOfPeople=Number of People meeting.new.submit=Submit meeting.new.return=Return to Calendar +meeting.new.occupied=Occupied actions.page.title=Actions actions.greeting=Hello @@ -110,6 +111,8 @@ admin.requests.validated=Validated admin.requests.validate=Validate admin.requests.no.found=No requests found admin.requests.return=Return to Dashboard +admin.requests.filter.button=Filter +admin.requests.filter.active=Filtered admin.meetings.page.title=Meeting Management admin.meetings.title=Manage Meetings admin.meetings.search.placeholder=Search meetings... @@ -130,3 +133,9 @@ admin.dashboard.users=Manage Users admin.dashboard.requests=Manage Requests admin.dashboard.meetings=Manage Meetings admin.dashboard.return=Return to User Actions + +password.change.title=Forgot Password +password.change.email=Email +password.change.submit=Submit +password.change.error=Error processing your request. Please try again. +password.change.newPassword=New Password diff --git a/src/main/resources/messages_es.properties b/src/main/resources/messages_es.properties index c1c31dfe44f44482b4b5be9df236c8cc10a65d3e..59cdb245607c793eae33b3d928201dd49076eb16 100644 --- a/src/main/resources/messages_es.properties +++ b/src/main/resources/messages_es.properties @@ -1,4 +1,4 @@ -header.title=Planificador de Reuniones +header.title=MeetingPlannr header.subtitle=Organiza tus reuniones fácilmente footer.copyright=© 2023 Planificador de Reuniones. Todos los derechos reservados. @@ -10,7 +10,7 @@ register.phone=Número de teléfono register.birthdate=Fecha de nacimiento register.password=Contraseña register.submit=Enviar -register.login=¿Ya tienes una cuenta? Inicia sesión aquí +register.login=Iniciar sesión login.title=Iniciar sesión login.email=Correo electrónico login.password=Contraseña @@ -18,6 +18,12 @@ login.submit=Iniciar sesión login.register=Regístrate aquí login.error=Nombre de usuario o contraseña inválidos +password.change.title=Olvidé mi contraseña +password.change.email=Correo electrónico +password.change.submit=Enviar +password.change.error=Error al procesar tu solicitud. Por favor, inténtalo de nuevo. +password.change.newPassword=Nueva contraseña + profile.page.title=Perfil profile.title=Tu Perfil profile.picture=Foto de Perfil @@ -48,6 +54,7 @@ meeting.new.description=Descripción meeting.new.numberOfPeople=Número de Personas meeting.new.submit=Enviar meeting.new.return=Volver al Calendario +meeting.new.occupied=Ocupado actions.page.title=Acciones actions.greeting=Hola @@ -108,6 +115,8 @@ admin.requests.date.time=Fecha y Hora admin.requests.requester=Solicitante admin.requests.validated=Validada admin.requests.validate=Validar +admin.requests.filter.button=Filtrar +admin.requests.filter.active=Filtrado admin.requests.no.found=No se encontraron solicitudes admin.requests.return=Volver al Panel de Control admin.meetings.page.title=Gestión de Reuniones diff --git a/src/main/resources/messages_fr.properties b/src/main/resources/messages_fr.properties index 48267a4f8c8a2b4aa34a686fa0a7017972ef9b32..8b22b6b3d1449f90c7e340bf0896b7afa40d44f2 100644 --- a/src/main/resources/messages_fr.properties +++ b/src/main/resources/messages_fr.properties @@ -1,4 +1,4 @@ -header.title=Planificateur de Réunions +header.title=MeetingPlannr header.subtitle=Organisez vos réunions facilement footer.copyright=© 2023 Planificateur de Réunions. Tous droits réservés. @@ -48,6 +48,7 @@ meeting.new.description=Description meeting.new.numberOfPeople=Nombre de Personnes meeting.new.submit=Soumettre meeting.new.return=Retour au Calendrier +meeting.new.occupied=Occupé actions.page.title=Actions actions.greeting=Bonjour @@ -110,6 +111,8 @@ admin.requests.validated=Validée admin.requests.validate=Valider admin.requests.no.found=Aucune demande trouvée admin.requests.return=Retour au Tableau de Bord +admin.requests.filter.button=Filtrer +admin.requests.filter.active=Filtré admin.meetings.page.title=Gestion des Réunions admin.meetings.title=Gérer les Réunions admin.meetings.search.placeholder=Rechercher des réunions... @@ -130,3 +133,9 @@ admin.dashboard.users=Gérer les Utilisateurs admin.dashboard.requests=Gérer les Demandes admin.dashboard.meetings=Gérer les Réunions admin.dashboard.return=Retour aux Actions Utilisateur + +password.change.title=Mot de passe oublié +password.change.email=Email +password.change.submit=Soumettre +password.change.error=Erreur lors du traitement de votre demande. Veuillez réessayer. +password.change.newPassword=Nouveau mot de passe diff --git a/src/main/resources/static/styles/main.css b/src/main/resources/static/styles/main.css index c85eee2b874722af88d5ed63416b6fbf5beeb1d8..691c5065e031706a19feec4cac37eeae63c3a83c 100644 --- a/src/main/resources/static/styles/main.css +++ b/src/main/resources/static/styles/main.css @@ -60,6 +60,7 @@ h1, h2 { text-align: center; border: 1px solid #ddd; min-height: 80px; + transition: background-color 0.3s ease; } .weekend { @@ -154,6 +155,21 @@ button.back:hover { background-color: #0056b3; } +button.filter { + background-color: #ffc107; +} + +button.filter:hover { + background-color: #e0a800; +} + +button.filter.active { + background-color: #28a745; +} + +button.filter.active:hover { + background-color: #218838; +} /* Formulaires */ .form-container, .container { @@ -609,3 +625,32 @@ button.back:hover { .language-switcher span { color: rgba(255, 255, 255, 0.5); } + +/* Styles pour la pagination */ +.pagination { + margin: 20px 0; + display: flex; + justify-content: center; + gap: 10px; +} + +.pagination-link { + padding: 10px 15px; + border: 1px solid #ddd; + border-radius: 4px; + background: white; + color: #007bff; + text-decoration: none; + transition: background-color 0.3s, color 0.3s; +} + +.pagination-link:hover { + background-color: #007bff; + color: white; +} + +.pagination-link.active { + background-color: #007bff; + color: white; + font-weight: bold; +} diff --git a/src/main/resources/static/uploads/logo.jpg b/src/main/resources/static/uploads/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..80505741dcf815b0304f1f7fdd35d7678b786d73 Binary files /dev/null and b/src/main/resources/static/uploads/logo.jpg differ diff --git a/src/main/webapp/WEB-INF/jsp/admin/meetings.jsp b/src/main/webapp/WEB-INF/jsp/admin/meetings.jsp index b546775e823ab09c8143a170a4e9dd362c3f4688..1e97791c5ec7325c0784a78a00ac1f218edcb93e 100644 --- a/src/main/webapp/WEB-INF/jsp/admin/meetings.jsp +++ b/src/main/webapp/WEB-INF/jsp/admin/meetings.jsp @@ -62,7 +62,7 @@ for (int i = 1; i <= totalPages; i++) { %> <a href="?page=<%= i %>&search=${param.search}" - class="<%= i == currentPage ? "active" : "" %>"> + class="pagination-link <%= i == currentPage ? "active" : "" %>"> <%= i %> </a> <% diff --git a/src/main/webapp/WEB-INF/jsp/admin/requests.jsp b/src/main/webapp/WEB-INF/jsp/admin/requests.jsp index 811b96d509b6b7f33504997ca2b5da223fcea21f..e04224351b0b3ec4dc123f60401d6565ef6a9aee 100644 --- a/src/main/webapp/WEB-INF/jsp/admin/requests.jsp +++ b/src/main/webapp/WEB-INF/jsp/admin/requests.jsp @@ -18,6 +18,9 @@ <input type="text" name="search" placeholder="<spring:message code="admin.requests.search.placeholder"/>" value="${param.search}"> <button type="submit" class="back"><spring:message code="admin.requests.search.button"/></button> + <button type="submit" name="filter" value="${param.filter == 'true' ? 'false' : 'true'}" class="filter ${param.filter == 'true' ? 'active' : ''}"> + <spring:message code="${param.filter == 'true' ? 'admin.requests.filter.active' : 'admin.requests.filter.button'}"/> + </button> </form> <div class="users-list"> @@ -37,7 +40,7 @@ %> <span class="banned-status"><spring:message code="admin.requests.validated"/></span> <% } else { %> - <form action="${pageContext.request.contextPath}/admin/requests/validate/<%= req.getRno() %>" method="post" style="display: inline;"> + <form action="${pageContext.request.contextPath}/admin/requests/validate/<%= req.getRno() %>?page=${param.page}&search=${param.search}&filter=${param.filter}" method="post" style="display: inline;"> <button type="submit" class="submit"><spring:message code="admin.requests.validate"/></button> </form> <% } %> @@ -59,8 +62,8 @@ int currentPage = (Integer) request.getAttribute("currentPage"); for (int i = 1; i <= totalPages; i++) { %> - <a href="?page=<%= i %>&search=${param.search}" - class="<%= i == currentPage ? "active" : "" %>"> + <a href="?page=<%= i %>&search=${param.search}&filter=${param.filter}" + class="pagination-link <%= i == currentPage ? "active" : "" %>"> <%= i %> </a> <% diff --git a/src/main/webapp/WEB-INF/jsp/admin/users.jsp b/src/main/webapp/WEB-INF/jsp/admin/users.jsp index 982f36a348b9f0d25a67080e13f815e7e1ac1334..6b8893812af97f0d8b59dc4bd4fd575ddb3f329f 100644 --- a/src/main/webapp/WEB-INF/jsp/admin/users.jsp +++ b/src/main/webapp/WEB-INF/jsp/admin/users.jsp @@ -63,7 +63,7 @@ for (int i = 1; i <= totalPages; i++) { %> <a href="?page=<%= i %>&search=${param.search}" - class="<%= i == currentPage ? "active" : "" %>"> + class="pagination-link <%= i == currentPage ? "active" : "" %>"> <%= i %> </a> <% diff --git a/src/main/webapp/WEB-INF/jsp/common/header.jsp b/src/main/webapp/WEB-INF/jsp/common/header.jsp index 2653ba9489d8789b1f0cd52884a210eb0c8d7fbf..ab5d84f2bf81c99c47ca8e0080eb9e26a2be4a0c 100644 --- a/src/main/webapp/WEB-INF/jsp/common/header.jsp +++ b/src/main/webapp/WEB-INF/jsp/common/header.jsp @@ -11,7 +11,9 @@ </div> <div class="logo"> <a href="${pageContext.request.contextPath}/user/listeAction"> - <span class="logo-icon">📅</span> + <span class="logo-icon"> + <img src="${pageContext.request.contextPath}/uploads/logo.jpg" alt="Logo" style="width: 50px; height: 50px;"> + </span> <h1><spring:message code="header.title"/></h1> </a> </div> diff --git a/src/main/webapp/WEB-INF/jsp/public/login.jsp b/src/main/webapp/WEB-INF/jsp/public/login.jsp index e435d4beaf2fa8808a7e9418044b631fbaa6c7f3..dfc1ccfaa0d7e2657e1b62e50d61398278fe4b87 100644 --- a/src/main/webapp/WEB-INF/jsp/public/login.jsp +++ b/src/main/webapp/WEB-INF/jsp/public/login.jsp @@ -20,9 +20,13 @@ <input type="password" id="password" name="password"> </div> <button type="submit" class="submit"><spring:message code="login.submit"/></button> + <br><br> <a href="register"> <spring:message code="login.register"/> </a> + <a href="password_change"> + <spring:message code="password.change.title"/> + </a> </form> <% String errorMessage = (String) request.getAttribute("errorMessage"); diff --git a/src/main/webapp/WEB-INF/jsp/public/passwordChange.jsp b/src/main/webapp/WEB-INF/jsp/public/passwordChange.jsp new file mode 100644 index 0000000000000000000000000000000000000000..964e264e6e1232ca61b957d20064891fc78f5947 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/public/passwordChange.jsp @@ -0,0 +1,50 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<!DOCTYPE html> +<html> +<head> + <title><spring:message code="password.change.title"/></title> + <link rel="stylesheet" href="${pageContext.request.contextPath}/styles/main.css"> +</head> +<body> + <%@ include file="../common/header.jsp" %> + <div class="container"> + <h2><spring:message code="password.change.title"/></h2> + <form action="${pageContext.request.contextPath}/public/perform_password_change" method="POST"> + <div class="form-group"> + <label for="email"><spring:message code="password.change.email"/></label> + <input type="email" id="email" name="email" required> + </div> + <div class="form-group"> + <label for="newPassword"><spring:message code="password.change.newPassword"/></label> + <input type="password" id="newPassword" name="newPassword" required> + </div> + <button type="submit" class="submit"><spring:message code="password.change.submit"/></button> + </form> + + <%-- Affichage du message d'erreur s'il existe --%> + <% + String errorMessage = (String) request.getAttribute("errorMessage"); + if (errorMessage != null) { + %> + <div class="error"><%= errorMessage %></div> + <% + } + %> + + <%-- Affichage du message de succès s'il existe --%> + <% + String successMessage = (String) request.getAttribute("successMessage"); + if (successMessage != null) { + %> + <div class="success"><%= successMessage %></div> + <% + } + %> + <a href="login"> + <spring:message code="register.login"/> + </a> + </div> + <%@ include file="../common/footer.jsp" %> +</body> +</html> diff --git a/src/main/webapp/WEB-INF/jsp/user/calendar.jsp b/src/main/webapp/WEB-INF/jsp/user/calendar.jsp index 56b159995843b541bda32109d3299223374ddfbd..dc485073506f07a37d3ca5868a848f0b2d44e834 100644 --- a/src/main/webapp/WEB-INF/jsp/user/calendar.jsp +++ b/src/main/webapp/WEB-INF/jsp/user/calendar.jsp @@ -7,6 +7,11 @@ <%@ page import="java.util.Locale" %> <%@ page import="fr.but.infoetu.meetingplannr.config.Config" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ page import="java.util.List" %> +<%@ page import="fr.but.infoetu.meetingplannr.pojo.Meeting" %> +<%@ page import="java.util.Map" %> +<%@ page import="java.util.Collections" %> + <!DOCTYPE html> <html> <head> @@ -19,19 +24,12 @@ <h1><spring:message code="calendar.title"/></h1> <% - String monthParam = request.getParameter("month"); - String yearParam = request.getParameter("year"); - LocalDate today = LocalDate.now(); - LocalDate currentDate; - - if (monthParam != null && yearParam != null) { - currentDate = LocalDate.of(Integer.parseInt(yearParam), Integer.parseInt(monthParam), 1); - } else { - currentDate = LocalDate.now().withDayOfMonth(1); - } - - LocalDate previousMonth = currentDate.minusMonths(1); - LocalDate nextMonth = currentDate.plusMonths(1); + LocalDate currentDate = (LocalDate) request.getAttribute("currentDate"); + LocalDate previousMonth = (LocalDate) request.getAttribute("previousMonth"); + LocalDate nextMonth = (LocalDate) request.getAttribute("nextMonth"); + LocalDate today = (LocalDate) request.getAttribute("today"); + Map<LocalDate, List<Meeting>> meetingsByDate = (Map<LocalDate, List<Meeting>>) request.getAttribute("meetingsByDate"); + int totalSlots = Config.createTimeSlots().size(); %> <div class="calendar-navigation"> @@ -56,27 +54,31 @@ <div class="calendar-header"><spring:message code="calendar.wednesday"/></div> <div class="calendar-header"><spring:message code="calendar.thursday"/></div> <div class="calendar-header"><spring:message code="calendar.friday"/></div> - <div class="calendar-header weekend"><spring:message code="calendar.saturday"/></div> - <div class="calendar-header weekend"><spring:message code="calendar.sunday"/></div> + <div class="calendar-header"><spring:message code="calendar.saturday"/></div> + <div class="calendar-header"><spring:message code="calendar.sunday"/></div> <% LocalDate date = currentDate; int month = date.getMonthValue(); - int firstDayOfWeek = date.getDayOfWeek().getValue(); - + %> + + <% for (int i = 1; i < firstDayOfWeek; i++) { %><div class="calendar-day"></div><% } while (date.getMonthValue() == month) { DayOfWeek dow = date.getDayOfWeek(); - boolean isWeekend = dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY; boolean isPast = date.isBefore(today); boolean isConfiguredDay = Config.WEEK_DAYS.contains(dow.name()); - String dayClass = isWeekend ? "weekend" : isPast ? "past-day" : "active-day"; + List<Meeting> meetingsForDay = meetingsByDate.getOrDefault(date, Collections.emptyList()); + int bookedSlots = meetingsForDay.size(); + double availabilityRatio = (double) bookedSlots / totalSlots; + String color = isConfiguredDay ? String.format("rgb(255, %d, %d)", (int) (255 * (1 - availabilityRatio)), (int) (255 * (1 - availabilityRatio))) : ""; + String dayClass = !isConfiguredDay ? "weekend" : isPast ? "past-day" : "active-day"; %> - <div class="calendar-day <%= dayClass %>"> + <div class="calendar-day <%= dayClass %>" style="<%= isConfiguredDay ? "background-color: " + color : "" %>;"> <%= date.getDayOfMonth() %> <% if (!isPast && isConfiguredDay) { %> <form action="${pageContext.request.contextPath}/user/meetings/new" method="get"> diff --git a/src/main/webapp/WEB-INF/jsp/user/newMeeting.jsp b/src/main/webapp/WEB-INF/jsp/user/newMeeting.jsp index 1967c1151b5ca0f384e2c7c2a85120ec965ff6a6..f1d7ae1f99f8fd7cf7a53494eddec69edcdfcacb 100644 --- a/src/main/webapp/WEB-INF/jsp/user/newMeeting.jsp +++ b/src/main/webapp/WEB-INF/jsp/user/newMeeting.jsp @@ -30,10 +30,14 @@ for (java.sql.Time timeSlot : Config.createTimeSlots()) { LocalTime time = timeSlot.toLocalTime(); + String formattedTime = String.format("%02d:%02d", time.getHour(), time.getMinute()); if (isTimeAvailable.apply(time)) { - String formattedTime = String.format("%02d:%02d", time.getHour(), time.getMinute()); %> <option value="<%= formattedTime %>"><%= formattedTime %></option> + <% + } else { + %> + <option value="<%= formattedTime %>" disabled style="color: gray;"> <%= formattedTime %> <spring:message code="meeting.new.occupied"/></option> <% } }