diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java index 57dab7c..bcaf024 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/TaskRepositoryImpl.java @@ -97,4 +97,24 @@ public class TaskRepositoryImpl implements TaskRepository, PanacheRepository findTasksWorkedByUserEmailsInPeriod(List emails, LocalDateTime start, LocalDateTime end) { + if (emails.isEmpty()) return List.of(); + return find("user.email in ?1", emails).list().stream() + .filter(entity -> entity.getHistory().stream().anyMatch(h -> + Objects.nonNull(h.getStartAt()) && + !h.getStartAt().isAfter(end) && + (Objects.isNull(h.getEndAt()) || !h.getEndAt().isBefore(start)))) + .map(TaskEntityMapper::toDomain) + .toList(); + } + + @Override + public List findTasksFinishedByUserEmailsInPeriod(List emails, LocalDateTime start, LocalDateTime end) { + if (emails.isEmpty()) return List.of(); + return find("user.email in ?1 and status = ?2 and finishedAt >= ?3 and finishedAt <= ?4", + emails, TaskStatus.COMPLETED, start, end) + .list().stream().map(TaskEntityMapper::toDomain).toList(); + } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/orgdashboard/OrgDashboardUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/orgdashboard/OrgDashboardUseCase.java index ed361ec..5f780e1 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/orgdashboard/OrgDashboardUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/orgdashboard/OrgDashboardUseCase.java @@ -7,6 +7,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import net.beetechgroup.beetask.entities.organization.UserOrganization; import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; @@ -33,11 +34,19 @@ public class OrgDashboardUseCase { public OrgDashboardOutput execute(OrgDashboardInput input) { authorizer.execute(input.userEmail(), input.organizationId()); - List workedTasks = taskRepository.findTasksWorkedByOrgInPeriod( - input.organizationId(), input.startDate(), input.endDate() + List members = userOrganizationRepository.findByOrganizationIdAndStatus( + input.organizationId(), UserOrganizationStatus.ACTIVE ); - List finishedTasks = taskRepository.findTasksFinishedByOrgInPeriod( - input.organizationId(), input.startDate(), input.endDate() + + List memberEmails = members.stream() + .map(uo -> uo.getUser().getEmail()) + .toList(); + + List workedTasks = taskRepository.findTasksWorkedByUserEmailsInPeriod( + memberEmails, input.startDate(), input.endDate() + ); + List finishedTasks = taskRepository.findTasksFinishedByUserEmailsInPeriod( + memberEmails, input.startDate(), input.endDate() ); Map projectStatsMap = new HashMap<>(); @@ -46,15 +55,14 @@ public class OrgDashboardUseCase { for (Task task : workedTasks) { long minutes = calculateMinutesInPeriod(task, input.startDate(), input.endDate()); - if (task.getProject() != null) { - Long projectId = task.getProject().getId(); - OrgProjectStats existing = projectStatsMap.getOrDefault(projectId, - new OrgProjectStats(projectId, task.getProject().getName(), 0L)); - projectStatsMap.put(projectId, new OrgProjectStats( - projectId, existing.projectName(), existing.totalMinutes() + minutes)); - } + Long projectId = Objects.nonNull(task.getProject()) ? task.getProject().getId() : null; + String projectName = Objects.nonNull(task.getProject()) ? task.getProject().getName() : "Geral"; + OrgProjectStats existing = projectStatsMap.getOrDefault(projectId, + new OrgProjectStats(projectId, projectName, 0L)); + projectStatsMap.put(projectId, new OrgProjectStats( + projectId, existing.projectName(), existing.totalMinutes() + minutes)); - if (task.getUser() != null) { + if (Objects.nonNull(task.getUser())) { memberMinutesMap.merge(task.getUser().getEmail(), minutes, Long::sum); } } @@ -62,7 +70,7 @@ public class OrgDashboardUseCase { List topTasks = workedTasks.stream() .map(t -> { long minutes = calculateMinutesInPeriod(t, input.startDate(), input.endDate()); - String projectName = t.getProject() != null ? t.getProject().getName() : "-"; + String projectName = Objects.nonNull(t.getProject()) ? t.getProject().getName() : "Geral"; return new OrgTopTask(t.getId(), t.getTitle(), projectName, minutes); }) .sorted(Comparator.comparingLong(OrgTopTask::totalMinutes).reversed()) @@ -71,15 +79,11 @@ public class OrgDashboardUseCase { Map finishedByMember = new HashMap<>(); for (Task task : finishedTasks) { - if (task.getUser() != null) { + if (Objects.nonNull(task.getUser())) { finishedByMember.merge(task.getUser().getEmail(), 1L, Long::sum); } } - List members = userOrganizationRepository.findByOrganizationIdAndStatus( - input.organizationId(), UserOrganizationStatus.ACTIVE - ); - List memberStats = members.stream() .map(uo -> { String email = uo.getUser().getEmail(); @@ -109,15 +113,15 @@ public class OrgDashboardUseCase { } private boolean isWithinPeriod(TaskHistoryItem item, LocalDateTime start, LocalDateTime end) { - if (item.getStartAt() == null) return false; + if (Objects.isNull(item.getStartAt())) return false; if (item.getStartAt().isAfter(end)) return false; - if (item.getEndAt() != null && item.getEndAt().isBefore(start)) return false; + if (Objects.nonNull(item.getEndAt()) && item.getEndAt().isBefore(start)) return false; return true; } private long calculateMinutesInRange(TaskHistoryItem item, LocalDateTime start, LocalDateTime end) { LocalDateTime effectiveStart = item.getStartAt().isBefore(start) ? start : item.getStartAt(); - LocalDateTime effectiveEnd = item.getEndAt() == null || item.getEndAt().isAfter(end) ? end : item.getEndAt(); + LocalDateTime effectiveEnd = Objects.isNull(item.getEndAt()) || item.getEndAt().isAfter(end) ? end : item.getEndAt(); if (effectiveStart.isAfter(effectiveEnd)) return 0; return Duration.between(effectiveStart, effectiveEnd).toMinutes(); } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/orgdashboard/memberdetail/MemberDetailUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/orgdashboard/memberdetail/MemberDetailUseCase.java index e1ea938..55a75c5 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/orgdashboard/memberdetail/MemberDetailUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/orgdashboard/memberdetail/MemberDetailUseCase.java @@ -67,13 +67,12 @@ public class MemberDetailUseCase { minutesPerMonth.merge(YearMonth.from(effectiveStart), minutes, Long::sum); } - if (Objects.nonNull(task.getProject())) { - Long projectId = task.getProject().getId(); - MemberDetailOutput.ProjectStats existing = projectStatsMap.getOrDefault(projectId, - new MemberDetailOutput.ProjectStats(projectId, task.getProject().getName(), 0L)); - projectStatsMap.put(projectId, new MemberDetailOutput.ProjectStats( - projectId, existing.projectName(), existing.totalMinutes() + minutes)); - } + Long projectId = Objects.nonNull(task.getProject()) ? task.getProject().getId() : null; + String projectName = Objects.nonNull(task.getProject()) ? task.getProject().getName() : "Geral"; + MemberDetailOutput.ProjectStats existing = projectStatsMap.getOrDefault(projectId, + new MemberDetailOutput.ProjectStats(projectId, projectName, 0L)); + projectStatsMap.put(projectId, new MemberDetailOutput.ProjectStats( + projectId, existing.projectName(), existing.totalMinutes() + minutes)); } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/TaskRepository.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/TaskRepository.java index d9a20c2..90c58b6 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/TaskRepository.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/TaskRepository.java @@ -16,4 +16,6 @@ public interface TaskRepository { List findTasksFinishedByOrgInPeriod(Long orgId, LocalDateTime start, LocalDateTime end); List findTasksWorkedByUserIdInPeriod(Long userId, LocalDateTime start, LocalDateTime end); List findTasksFinishedByUserIdInPeriod(Long userId, LocalDateTime start, LocalDateTime end); + List findTasksWorkedByUserEmailsInPeriod(List emails, LocalDateTime start, LocalDateTime end); + List findTasksFinishedByUserEmailsInPeriod(List emails, LocalDateTime start, LocalDateTime end); } \ No newline at end of file diff --git a/frontend/src/components/MemberDetailModal.tsx b/frontend/src/components/MemberDetailModal.tsx index fdb1af9..0275030 100644 --- a/frontend/src/components/MemberDetailModal.tsx +++ b/frontend/src/components/MemberDetailModal.tsx @@ -294,7 +294,7 @@ export const MemberDetailModal: React.FC = ({ const total = data.projectStats.reduce((s, x) => s + x.totalMinutes, 0); const pct = total > 0 ? ((p.totalMinutes / total) * 100).toFixed(0) : "0"; return ( -
+
{p.projectName} diff --git a/frontend/src/pages/OrgDashboardPage.tsx b/frontend/src/pages/OrgDashboardPage.tsx index 5d69f6f..ada1d28 100644 --- a/frontend/src/pages/OrgDashboardPage.tsx +++ b/frontend/src/pages/OrgDashboardPage.tsx @@ -246,7 +246,7 @@ export const OrgDashboardPage: React.FC = () => { .map((stat, i) => { const pct = totalMinutes > 0 ? ((stat.totalMinutes / totalMinutes) * 100).toFixed(1) : "0"; return ( -
+