From 5643053471244d7c99b1a51f48de565df1412be4 Mon Sep 17 00:00:00 2001 From: lukasmatusiewicz Date: Thu, 23 Apr 2026 10:52:17 +0200 Subject: [PATCH 1/4] feat: implement dashboard and task status toggle functionality --- .../controller/DashboardController.java | 45 +++++ .../controller/TaskController.java | 7 + .../com/lucc/taskmanager/init/DataLoader.java | 31 +-- .../java/com/lucc/taskmanager/model/Task.java | 35 ++++ .../repository/TaskRepository.java | 8 + .../lucc/taskmanager/service/TaskService.java | 40 ++++ src/main/resources/templates/dashboard.html | 180 ++++++++++++++++++ src/main/resources/templates/edit-task.html | 4 + src/main/resources/templates/tasks.html | 62 +++++- .../taskmanager/service/TaskServiceTest.java | 86 ++++++++- 10 files changed, 479 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/lucc/taskmanager/controller/DashboardController.java create mode 100644 src/main/resources/templates/dashboard.html diff --git a/src/main/java/com/lucc/taskmanager/controller/DashboardController.java b/src/main/java/com/lucc/taskmanager/controller/DashboardController.java new file mode 100644 index 0000000..cecd83d --- /dev/null +++ b/src/main/java/com/lucc/taskmanager/controller/DashboardController.java @@ -0,0 +1,45 @@ +package com.lucc.taskmanager.controller; + +import com.lucc.taskmanager.model.Task; +import com.lucc.taskmanager.model.User; +import com.lucc.taskmanager.service.TaskService; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Controller +@RequestMapping("/dashboard") +public class DashboardController { + + private final TaskService taskService; + + public DashboardController(TaskService taskService) { + this.taskService = taskService; + } + + @GetMapping + public String getDashboard(@AuthenticationPrincipal User user, Model model) { + Map stats = taskService.getDashboardStats(user); + model.addAllAttributes(stats); + + List tasks = taskService.getTasksByUser(user); + + // Data for status chart + Map statusCounts = tasks.stream() + .collect(Collectors.groupingBy(t -> t.getStatus().name(), Collectors.counting())); + model.addAttribute("statusCounts", statusCounts); + + // Data for priority chart + Map priorityCounts = tasks.stream() + .collect(Collectors.groupingBy(t -> t.getPriority().name(), Collectors.counting())); + model.addAttribute("priorityCounts", priorityCounts); + + return "dashboard"; + } +} diff --git a/src/main/java/com/lucc/taskmanager/controller/TaskController.java b/src/main/java/com/lucc/taskmanager/controller/TaskController.java index 954e088..f19a799 100644 --- a/src/main/java/com/lucc/taskmanager/controller/TaskController.java +++ b/src/main/java/com/lucc/taskmanager/controller/TaskController.java @@ -51,4 +51,11 @@ public void deleteTask(@PathVariable int taskId, @AuthenticationPrincipal @Param { taskService.deleteTask(taskId, user); } + + @PatchMapping("/{taskId}/toggle") + @Operation(summary = "Toggle task status between TODO and DONE") + public Task toggleTaskStatus(@PathVariable int taskId, @AuthenticationPrincipal @Parameter(hidden = true) User user) + { + return taskService.toggleTaskStatus(taskId, user); + } } diff --git a/src/main/java/com/lucc/taskmanager/init/DataLoader.java b/src/main/java/com/lucc/taskmanager/init/DataLoader.java index d1c9535..e8371e2 100644 --- a/src/main/java/com/lucc/taskmanager/init/DataLoader.java +++ b/src/main/java/com/lucc/taskmanager/init/DataLoader.java @@ -12,6 +12,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; +import java.time.LocalDate; import java.util.List; @Component @@ -39,20 +40,26 @@ public void run(String ... args) throws Exception userRepository.saveAll(List.of(admin, user)); System.out.println("Users loaded"); - Task adminTask1 = new Task("Pizza", "Pizza is a dish of Italian origin made by a mixture of fried bread and tomatoes, usually sliced thinly and topped with a slice of cheese.", admin, Status.TODO, Priority.MEDIUM); - Task adminTask2 = new Task("Burger", "A burger is a large, flat, round, steak dressed in lettuce, tomato, cheese, and meat sauce.", admin, Status.TODO, Priority.MEDIUM); - Task adminTask3 = new Task("Pasta", "Pasta is a food made from a mixture of flour, eggs, and water, typically cooked under high heat.", admin, Status.TODO, Priority.MEDIUM); - Task adminTask4 = new Task("Sandwich", "A sandwich is a flat bread with lettuce and tomato on one side, and a slice of cheese on the other side.", admin, Status.TODO, Priority.MEDIUM); - Task adminTask5 = new Task("Coffee", "Coffee is a brewed drink made from roasted coffee beans, ground coffee, and sugar.", admin, Status.TODO, Priority.MEDIUM); - Task adminTask6 = new Task("Coffee Shaker", "A coffee shaker is a container for storing and shaking coffee.", admin, Status.TODO, Priority.MEDIUM); - Task adminTask7 = new Task("Coffee Mug", "A coffee mug is a container for storing and shaking coffee.", admin, Status.TODO, Priority.MEDIUM); + LocalDate today = LocalDate.now(); + Task adminTask1 = new Task("Pizza", "Pizza description", admin, Status.TODO, Priority.MEDIUM, today.plusDays(2)); + Task adminTask2 = new Task("Burger", "Burger description", admin, Status.TODO, Priority.HIGH, today.minusDays(1)); // Overdue + Task adminTask3 = new Task("Pasta", "Pasta description", admin, Status.DONE, Priority.LOW, today.minusDays(2)); + adminTask3.setCompletionDate(today.minusDays(1)); // Completed this week + + Task adminTask4 = new Task("Sandwich", "Sandwich description", admin, Status.TODO, Priority.MEDIUM, today.plusDays(5)); + Task adminTask5 = new Task("Coffee", "Coffee description", admin, Status.TODO, Priority.MEDIUM, today.plusDays(1)); + Task adminTask6 = new Task("Coffee Shaker", "Coffee shaker description", admin, Status.TODO, Priority.MEDIUM, today.plusDays(3)); + Task adminTask7 = new Task("Coffee Mug", "Coffee mug description", admin, Status.DONE, Priority.MEDIUM, today.minusDays(10)); + adminTask7.setCompletionDate(today.minusDays(8)); // Completed more than a week ago - Task userTask1 = new Task("Pizza", "Pizza is a dish of Italian origin made by a mixture of fried bread and tomatoes, usually sliced thinly and topped with a slice of cheese.", user, Status.TODO, Priority.MEDIUM); - Task userTask2 = new Task("Burger", "A burger is a large, flat, round, steak dressed in lettuce, tomato, cheese, and meat sauce.", user, Status.TODO, Priority.MEDIUM); - Task userTask3 = new Task("Pasta", "Pasta is a food made from a mixture of flour, eggs, and water, typically cooked under high heat.", user, Status.TODO, Priority.MEDIUM); - Task userTask4 = new Task("Sandwich", "A sandwich is a flat bread with lettuce and tomato on one side, and a slice of cheese on the other side.", user, Status.TODO, Priority.MEDIUM); - Task userTask5 = new Task("Coffee", "Coffee is a brewed drink made from roasted coffee beans, ground coffee, and sugar.", user, Status.TODO, Priority.MEDIUM); + Task userTask1 = new Task("Study Spring Boot", "Learn about Spring Boot security and data.", user, Status.TODO, Priority.HIGH, today.plusDays(3)); + Task userTask2 = new Task("Workout", "Go to the gym for 1 hour.", user, Status.TODO, Priority.MEDIUM, today.minusDays(2)); // Overdue + Task userTask3 = new Task("Buy Groceries", "Buy milk, eggs, and bread.", user, Status.DONE, Priority.LOW, today.minusDays(1)); + userTask3.setCompletionDate(today); // Completed today + + Task userTask4 = new Task("Read a Book", "Read at least 30 pages.", user, Status.TODO, Priority.LOW, today.plusDays(7)); + Task userTask5 = new Task("Clean the Room", "Deep clean the bedroom.", user, Status.TODO, Priority.MEDIUM, today.plusDays(1)); taskRepository.saveAll(List.of(adminTask1, adminTask2, adminTask3, adminTask4, adminTask5, adminTask6, adminTask7, userTask1, userTask2, userTask3, userTask4, userTask5)); System.out.println("Tasks loaded"); diff --git a/src/main/java/com/lucc/taskmanager/model/Task.java b/src/main/java/com/lucc/taskmanager/model/Task.java index 19d620d..5b74415 100644 --- a/src/main/java/com/lucc/taskmanager/model/Task.java +++ b/src/main/java/com/lucc/taskmanager/model/Task.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import java.time.LocalDate; @Entity public class Task @@ -25,6 +26,10 @@ public class Task @Enumerated(EnumType.STRING) private Priority priority; + private LocalDate dueDate; + + private LocalDate completionDate; + @ManyToOne @JoinColumn(name = "user_id") private User user; @@ -40,6 +45,16 @@ public Task(String title, String description, User user, Status status, Priority this.priority = priority; } + public Task(String title, String description, User user, Status status, Priority priority, LocalDate dueDate) + { + this.title = title; + this.description = description; + this.user = user; + this.status = status; + this.priority = priority; + this.dueDate = dueDate; + } + public String getTitle() { return title; @@ -80,6 +95,26 @@ public void setPriority(Priority priority) this.priority = priority; } + public LocalDate getDueDate() + { + return dueDate; + } + + public void setDueDate(LocalDate dueDate) + { + this.dueDate = dueDate; + } + + public LocalDate getCompletionDate() + { + return completionDate; + } + + public void setCompletionDate(LocalDate completionDate) + { + this.completionDate = completionDate; + } + public User getUser() { return user; diff --git a/src/main/java/com/lucc/taskmanager/repository/TaskRepository.java b/src/main/java/com/lucc/taskmanager/repository/TaskRepository.java index 114bf62..eea33da 100644 --- a/src/main/java/com/lucc/taskmanager/repository/TaskRepository.java +++ b/src/main/java/com/lucc/taskmanager/repository/TaskRepository.java @@ -1,12 +1,20 @@ package com.lucc.taskmanager.repository; +import com.lucc.taskmanager.model.Status; import com.lucc.taskmanager.model.Task; import com.lucc.taskmanager.model.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.time.LocalDate; import java.util.List; public interface TaskRepository extends JpaRepository { List findByUser(User user); + + long countByUserAndStatus(User user, Status status); + + long countByUserAndStatusAndDueDateBefore(User user, Status status, LocalDate date); + + long countByUserAndStatusAndCompletionDateBetween(User user, Status status, LocalDate start, LocalDate end); } diff --git a/src/main/java/com/lucc/taskmanager/service/TaskService.java b/src/main/java/com/lucc/taskmanager/service/TaskService.java index 4d92a8a..9639692 100644 --- a/src/main/java/com/lucc/taskmanager/service/TaskService.java +++ b/src/main/java/com/lucc/taskmanager/service/TaskService.java @@ -1,11 +1,15 @@ package com.lucc.taskmanager.service; +import com.lucc.taskmanager.model.Status; import com.lucc.taskmanager.model.Task; import com.lucc.taskmanager.model.User; import com.lucc.taskmanager.repository.TaskRepository; import org.springframework.stereotype.Service; +import java.time.LocalDate; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Service public class TaskService @@ -25,6 +29,9 @@ public List getTasksByUser(User user) public Task addTask(Task task, User user) { task.setUser(user); + if (task.getStatus() == Status.DONE && task.getCompletionDate() == null) { + task.setCompletionDate(LocalDate.now()); + } return taskRepository.save(task); } @@ -41,10 +48,19 @@ public Task getTaskById(int taskId, User user) public Task updateTask(int taskId, Task updatedTask, User user) { Task existingTask = getTaskById(taskId, user); + + // Handle completion date logic + if (existingTask.getStatus() != Status.DONE && updatedTask.getStatus() == Status.DONE) { + existingTask.setCompletionDate(LocalDate.now()); + } else if (existingTask.getStatus() == Status.DONE && updatedTask.getStatus() != Status.DONE) { + existingTask.setCompletionDate(null); + } + existingTask.setTitle(updatedTask.getTitle()); existingTask.setDescription(updatedTask.getDescription()); existingTask.setStatus(updatedTask.getStatus()); existingTask.setPriority(updatedTask.getPriority()); + existingTask.setDueDate(updatedTask.getDueDate()); return taskRepository.save(existingTask); } @@ -57,4 +73,28 @@ public void deleteTask(int taskId, User user) taskRepository.delete(task); } + + public Map getDashboardStats(User user) { + LocalDate now = LocalDate.now(); + LocalDate weekAgo = now.minusDays(7); + + Map stats = new HashMap<>(); + stats.put("pending", taskRepository.countByUserAndStatus(user, Status.TODO)); + stats.put("overdue", taskRepository.countByUserAndStatusAndDueDateBefore(user, Status.TODO, now)); + stats.put("completedThisWeek", taskRepository.countByUserAndStatusAndCompletionDateBetween(user, Status.DONE, weekAgo, now)); + + return stats; + } + + public Task toggleTaskStatus(int taskId, User user) { + Task task = getTaskById(taskId, user); + if (task.getStatus() == Status.TODO) { + task.setStatus(Status.DONE); + task.setCompletionDate(LocalDate.now()); + } else { + task.setStatus(Status.TODO); + task.setCompletionDate(null); + } + return taskRepository.save(task); + } } diff --git a/src/main/resources/templates/dashboard.html b/src/main/resources/templates/dashboard.html new file mode 100644 index 0000000..443235f --- /dev/null +++ b/src/main/resources/templates/dashboard.html @@ -0,0 +1,180 @@ + + + + + + Dashboard - Task Manager + + + + + + + + + + + +
+

Task Dashboard

+ + +
+
+
+
+
Pending Tasks
+

0

+
+
+
+
+
+
+
Overdue Tasks
+

0

+
+
+
+
+
+
+
Completed (7 Days)
+

0

+
+
+
+
+ + +
+
+
+
+
Tasks by Status
+
+ +
+
+
+
+
+
+
+
Tasks by Priority
+
+ +
+
+
+
+
+
+ + + + + + + + diff --git a/src/main/resources/templates/edit-task.html b/src/main/resources/templates/edit-task.html index 2b2bd6e..07299cf 100644 --- a/src/main/resources/templates/edit-task.html +++ b/src/main/resources/templates/edit-task.html @@ -46,6 +46,10 @@

Edit Task

+
+ + +
Cancel diff --git a/src/main/resources/templates/tasks.html b/src/main/resources/templates/tasks.html index 6b9214d..5f41f15 100644 --- a/src/main/resources/templates/tasks.html +++ b/src/main/resources/templates/tasks.html @@ -9,17 +9,32 @@ + + @@ -37,12 +52,22 @@

My Tasks

+
+ + Due: + +
+ Edit
@@ -83,6 +108,10 @@

Add New Task

+
+ + +
@@ -95,6 +124,29 @@

Add New Task