From 1b337e53c246fa9ff699b46a4c7117d4b7d62073 Mon Sep 17 00:00:00 2001 From: Gabriel Santana de Menezes Date: Fri, 22 May 2026 16:06:24 -0400 Subject: [PATCH] feat: add periodStats to dashboard output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adiciona breakdown diário/mensal ao endpoint /dashboard do usuário: - Novo record DashboardPeriodStats com year, month, day, finishedTasksCount e totalMinutesWorked - DashboardUseCase agrupa history items por LocalDate para minutos e finishedAt para contagem de tarefas - Lógica groupedBy: ≤62 dias → DAY, caso contrário → MONTH Co-Authored-By: Claude Sonnet 4.6 --- .../usecase/dashboard/DashboardOutput.java | 4 +- .../dashboard/DashboardPeriodStats.java | 9 +++ .../usecase/dashboard/DashboardUseCase.java | 57 +++++++++++++++---- 3 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardPeriodStats.java diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardOutput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardOutput.java index 170dc7d..3a8e513 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardOutput.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardOutput.java @@ -8,5 +8,7 @@ public record DashboardOutput( List projectStats, List categoryStats, List yesterdayTasks, - List finishedTasksInPeriod + List finishedTasksInPeriod, + List periodStats, + String groupedBy ) {} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardPeriodStats.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardPeriodStats.java new file mode 100644 index 0000000..57cc88d --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardPeriodStats.java @@ -0,0 +1,9 @@ +package net.beetechgroup.beetask.usecase.dashboard; + +public record DashboardPeriodStats( + int year, + int month, + int day, + int finishedTasksCount, + long totalMinutesWorked +) {} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardUseCase.java index ae6bece..eddb09b 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/dashboard/DashboardUseCase.java @@ -1,13 +1,17 @@ package net.beetechgroup.beetask.usecase.dashboard; import java.time.Duration; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.TreeMap; import net.beetechgroup.beetask.entities.Category; import net.beetechgroup.beetask.entities.task.Task; @@ -45,28 +49,36 @@ public class DashboardUseCase { long totalMinutes = 0; Map projectStatsMap = new HashMap<>(); Map categoryStatsMap = new HashMap<>(); + // [0] = totalMinutesWorked, [1] = finishedTasksCount grouped by day key + Map periodMap = new TreeMap<>(); for (Task task : workedTasks) { - long taskMinutesInPeriod = task.getHistory().stream() - .filter(h -> isWithinPeriod(h, input.startDate(), input.endDate())) - .mapToLong(h -> calculateMinutesInRange(h, input.startDate(), input.endDate())) - .sum(); - - totalMinutes += taskMinutesInPeriod; + for (TaskHistoryItem h : task.getHistory()) { + if (!isWithinPeriod(h, input.startDate(), input.endDate())) continue; + long minutes = calculateMinutesInRange(h, input.startDate(), input.endDate()); + totalMinutes += minutes; + LocalDate day = h.getStartAt().toLocalDate(); + long[] bucket = periodMap.computeIfAbsent(day, k -> new long[]{0L, 0L}); + bucket[0] += minutes; + } if (Objects.nonNull(task.getProject())) { + long taskMinutesInPeriod = task.getHistory().stream() + .filter(h -> isWithinPeriod(h, input.startDate(), input.endDate())) + .mapToLong(h -> calculateMinutesInRange(h, input.startDate(), input.endDate())) + .sum(); Long projectId = task.getProject().getId(); DashboardProjectStats stats = projectStatsMap.getOrDefault(projectId, new DashboardProjectStats(projectId, task.getProject().getName(), 0L)); - projectStatsMap.put(projectId, new DashboardProjectStats( - projectId, - stats.projectName(), - stats.totalMinutes() + taskMinutesInPeriod - )); + projectId, stats.projectName(), stats.totalMinutes() + taskMinutesInPeriod)); } if (Objects.nonNull(task.getCategory())) { + long taskMinutesInPeriod = task.getHistory().stream() + .filter(h -> isWithinPeriod(h, input.startDate(), input.endDate())) + .mapToLong(h -> calculateMinutesInRange(h, input.startDate(), input.endDate())) + .sum(); Category cat = task.getCategory(); DashboardCategoryStats existing = categoryStatsMap.getOrDefault(cat.getId(), new DashboardCategoryStats(cat.getId(), cat.getName(), cat.getColor(), cat.getIcon(), 0L)); @@ -76,12 +88,33 @@ public class DashboardUseCase { } } + for (Task task : finishedTasks) { + if (Objects.isNull(task.getFinishedAt())) continue; + LocalDate day = task.getFinishedAt().toLocalDate(); + long[] bucket = periodMap.computeIfAbsent(day, k -> new long[]{0L, 0L}); + bucket[1]++; + } + + long daySpan = ChronoUnit.DAYS.between(input.startDate().toLocalDate(), input.endDate().toLocalDate()); + String groupedBy = daySpan <= 62 ? "DAY" : "MONTH"; + + List periodStats = periodMap.entrySet().stream() + .map(e -> new DashboardPeriodStats( + e.getKey().getYear(), e.getKey().getMonthValue(), e.getKey().getDayOfMonth(), + (int) e.getValue()[1], e.getValue()[0])) + .sorted(Comparator.comparingInt(DashboardPeriodStats::year) + .thenComparingInt(DashboardPeriodStats::month) + .thenComparingInt(DashboardPeriodStats::day)) + .toList(); + DashboardOutput output = new DashboardOutput( totalMinutes, new ArrayList<>(projectStatsMap.values()), new ArrayList<>(categoryStatsMap.values()), yesterdayTasks.stream().map(CreateTaskMapper::toCreateTaskOutput).toList(), - finishedTasks.stream().map(CreateTaskMapper::toCreateTaskOutput).toList() + finishedTasks.stream().map(CreateTaskMapper::toCreateTaskOutput).toList(), + periodStats, + groupedBy ); LOGGER.infof("Dashboard calculated for user %s with %d worked tasks, %d finished tasks and %d total minutes", input.userEmail(), workedTasks.size(), finishedTasks.size(), totalMinutes);