diff --git a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/AvgHoursPerSP.jsx b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/AvgHoursPerSP.jsx
index ccb35a143..d1b28f76b 100644
--- a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/AvgHoursPerSP.jsx
+++ b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/AvgHoursPerSP.jsx
@@ -11,8 +11,8 @@ export default function AvgHoursPerSP({ sprintId }) {
const [error, setError] = useState('');
useEffect(() => {
- if (!sprintId) { setLoading(false); setData(null); return; }
setLoading(true);
+ setError('');
dashboardService.efficiency(project.id, sprintId)
.then(setData)
.catch((err) => setError(err.message))
diff --git a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/BlockedAlert.jsx b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/BlockedAlert.jsx
index b8ac5789a..ca03fc976 100644
--- a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/BlockedAlert.jsx
+++ b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/BlockedAlert.jsx
@@ -11,8 +11,8 @@ export default function BlockedAlert({ sprintId }) {
const [error, setError] = useState('');
useEffect(() => {
- if (!sprintId) { setLoading(false); setTasks([]); return; }
setLoading(true);
+ setError('');
dashboardService.blockedTasks(project.id, sprintId)
.then(setTasks)
.catch((err) => setError(err.message))
diff --git a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/CompletionRate.jsx b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/CompletionRate.jsx
index 0f50ec847..3526650af 100644
--- a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/CompletionRate.jsx
+++ b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/CompletionRate.jsx
@@ -11,8 +11,8 @@ export default function CompletionRate({ sprintId }) {
const [error, setError] = useState('');
useEffect(() => {
- if (!sprintId) { setLoading(false); setData(null); return; }
setLoading(true);
+ setError('');
dashboardService.sprintSummary(project.id, sprintId)
.then(setData)
.catch((err) => setError(err.message))
@@ -21,7 +21,7 @@ export default function CompletionRate({ sprintId }) {
if (loading) return ;
if (error) return
{error}
;
- if (!data) return Select a sprint to view data.
;
+ if (!data) return null;
const pct = data.completionPercentage ?? 0;
const color = pct >= 80 ? '#15803D' : pct >= 50 ? '#003865' : '#C74634';
diff --git a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/EfficiencyChart.jsx b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/EfficiencyChart.jsx
index f64423c73..0e088e33e 100644
--- a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/EfficiencyChart.jsx
+++ b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/EfficiencyChart.jsx
@@ -43,8 +43,8 @@ export default function EfficiencyChart({ sprintId }) {
const [error, setError] = useState('');
useEffect(() => {
- if (!sprintId) { setLoading(false); setData(null); return; }
setLoading(true);
+ setError('');
dashboardService.efficiency(project.id, sprintId)
.then(setData)
.catch((err) => setError(err.message))
@@ -53,7 +53,7 @@ export default function EfficiencyChart({ sprintId }) {
if (loading) return ;
if (error) return {error}
;
- if (!data) return Select a sprint to view data.
;
+ if (!data) return null;
if (!data.members?.length) return No completed tasks in this sprint.
;
const handleExport = () => {
@@ -62,7 +62,7 @@ export default function EfficiencyChart({ sprintId }) {
'SP Completados': m.spCompleted,
'Horas Reales': parseFloat(m.actualHours.toFixed(1)),
}));
- exportCsv(rows, `efficiency-sprint-${sprintId}`);
+ exportCsv(rows, sprintId ? `efficiency-sprint-${sprintId}` : 'efficiency-all-sprints');
};
const chartData = data.members.map((m) => ({
diff --git a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/HoursPerMember.jsx b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/HoursPerMember.jsx
index bfd131bca..7a32c91e8 100644
--- a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/HoursPerMember.jsx
+++ b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/HoursPerMember.jsx
@@ -17,8 +17,8 @@ export default function HoursPerMember({ sprintId }) {
const [error, setError] = useState('');
useEffect(() => {
- if (!sprintId) { setLoading(false); setData(null); return; }
setLoading(true);
+ setError('');
dashboardService.efficiency(project.id, sprintId)
.then(setData)
.catch((err) => setError(err.message))
@@ -27,7 +27,7 @@ export default function HoursPerMember({ sprintId }) {
if (loading) return ;
if (error) return {error}
;
- if (!data) return Select a sprint to view data.
;
+ if (!data) return null;
if (!data.members?.length) return No hours logged yet.
;
const sorted = [...data.members].sort((a, b) => b.actualHours - a.actualHours);
diff --git a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/SprintSummary.jsx b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/SprintSummary.jsx
index 4a9b64c68..7e9b7194f 100644
--- a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/SprintSummary.jsx
+++ b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/SprintSummary.jsx
@@ -14,8 +14,8 @@ export default function SprintSummary({ sprintId }) {
const [error, setError] = useState('');
useEffect(() => {
- if (!sprintId) { setLoading(false); setData(null); return; }
setLoading(true);
+ setError('');
dashboardService.sprintSummary(project.id, sprintId)
.then(setData)
.catch((err) => setError(err.message))
@@ -24,7 +24,7 @@ export default function SprintSummary({ sprintId }) {
if (loading) return ;
if (error) return {error}
;
- if (!data) return Select a sprint to view data.
;
+ if (!data) return null;
const counts = data.statusCounts || {};
diff --git a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/WorkloadTable.jsx b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/WorkloadTable.jsx
index 1957de9ba..35c73cd81 100644
--- a/MtdrSpring/backend/src/main/frontend/src/components/dashboard/WorkloadTable.jsx
+++ b/MtdrSpring/backend/src/main/frontend/src/components/dashboard/WorkloadTable.jsx
@@ -16,8 +16,8 @@ export default function WorkloadTable({ sprintId }) {
const [mode, setMode] = useState('tasks');
useEffect(() => {
- if (!sprintId) { setLoading(false); setData([]); return; }
setLoading(true);
+ setError('');
dashboardService.workload(project.id, sprintId)
.then(setData)
.catch((err) => setError(err.message))
@@ -26,7 +26,7 @@ export default function WorkloadTable({ sprintId }) {
if (loading) return ;
if (error) return {error}
;
- if (!data.length) return Select a sprint to view data.
;
+ if (!data.length) return No hay datos disponibles.
;
const getValue = (member, status) =>
mode === 'tasks'
@@ -48,7 +48,7 @@ export default function WorkloadTable({ sprintId }) {
'SP BLOCKED': m.storyPoints?.BLOCKED ?? 0,
'SP DONE': m.storyPoints?.DONE ?? 0,
}));
- exportCsv(rows, `workload-sprint-${sprintId}`);
+ exportCsv(rows, sprintId ? `workload-sprint-${sprintId}` : 'workload-all-sprints');
};
return (
diff --git a/MtdrSpring/backend/src/main/frontend/src/pages/DashboardPage.jsx b/MtdrSpring/backend/src/main/frontend/src/pages/DashboardPage.jsx
index 8e6544873..dbfcf0b84 100644
--- a/MtdrSpring/backend/src/main/frontend/src/pages/DashboardPage.jsx
+++ b/MtdrSpring/backend/src/main/frontend/src/pages/DashboardPage.jsx
@@ -38,8 +38,10 @@ export default function DashboardPage() {
const [sprints, setSprints] = useState([]);
const [selectedSprintId, setSelectedSprintId] = useState(null);
+ const [sprintsLoaded, setSprintsLoaded] = useState(false);
useEffect(() => {
+ setSprintsLoaded(false);
sprintService.list(project.id).then((data) => {
setSprints(data);
const active = data.find((s) => s.status === 'ACTIVE');
@@ -50,9 +52,9 @@ export default function DashboardPage() {
.filter((s) => s.status === 'CLOSED')
.sort((a, b) => new Date(b.endDate) - new Date(a.endDate));
if (closed.length > 0) setSelectedSprintId(closed[0].id);
- else if (data.length > 0) setSelectedSprintId(data[0].id);
+ else setSelectedSprintId(null);
}
- }).catch(() => {});
+ }).catch(() => {}).finally(() => setSprintsLoaded(true));
}, [project.id]);
const sid = selectedSprintId || null;
@@ -75,7 +77,14 @@ export default function DashboardPage() {
/>
-
+ {!sprintsLoaded && (
+
+ {[...Array(6)].map((_, i) => (
+
+ ))}
+
+ )}
+ {sprintsLoaded &&
{/* Sprint Summary */}
@@ -132,7 +141,7 @@ export default function DashboardPage() {
-
+
}
);
}
diff --git a/MtdrSpring/backend/src/main/frontend/src/services/dashboardService.js b/MtdrSpring/backend/src/main/frontend/src/services/dashboardService.js
index 7a88d5959..98f278fce 100644
--- a/MtdrSpring/backend/src/main/frontend/src/services/dashboardService.js
+++ b/MtdrSpring/backend/src/main/frontend/src/services/dashboardService.js
@@ -2,21 +2,27 @@ import { apiFetch } from './api';
export const dashboardService = {
sprintSummary: (projectId, sprintId) => {
- const q = sprintId ? `?sprintId=${sprintId}` : '';
- return apiFetch(`/projects/${projectId}/dashboard/sprint-summary${q}`);
+ const params = new URLSearchParams();
+ if (sprintId) params.set('sprintId', sprintId);
+ else params.set('allSprints', 'true');
+ return apiFetch(`/projects/${projectId}/dashboard/sprint-summary?${params}`);
},
velocity: (projectId, count = 5) =>
apiFetch(`/projects/${projectId}/dashboard/velocity?sprints=${count}`),
efficiency: (projectId, sprintId) => {
- const q = sprintId ? `?sprintId=${sprintId}` : '';
- return apiFetch(`/projects/${projectId}/dashboard/efficiency${q}`);
+ const params = new URLSearchParams();
+ if (sprintId) params.set('sprintId', sprintId);
+ else params.set('allSprints', 'true');
+ return apiFetch(`/projects/${projectId}/dashboard/efficiency?${params}`);
},
workload: (projectId, sprintId) => {
- const q = sprintId ? `?sprintId=${sprintId}` : '';
- return apiFetch(`/projects/${projectId}/dashboard/workload${q}`);
+ const params = new URLSearchParams();
+ if (sprintId) params.set('sprintId', sprintId);
+ else params.set('allSprints', 'true');
+ return apiFetch(`/projects/${projectId}/dashboard/workload?${params}`);
},
backlog: (projectId) =>
diff --git a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/project/dashboard/DashboardController.java b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/project/dashboard/DashboardController.java
index fb5a8d0ea..013666a8b 100644
--- a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/project/dashboard/DashboardController.java
+++ b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/project/dashboard/DashboardController.java
@@ -24,11 +24,12 @@ public class DashboardController {
private final DashboardService dashboardService;
@GetMapping("/sprint-summary")
- @Operation(summary = "Sprint summary — uses active sprint if sprintId is omitted")
+ @Operation(summary = "Sprint summary — uses active sprint if sprintId is omitted, all sprints if allSprints=true")
public ResponseEntity sprintSummary(@PathVariable Long projectId,
@RequestParam(required = false) Long sprintId,
+ @RequestParam(defaultValue = "false") boolean allSprints,
Authentication auth) {
- return ResponseEntity.ok(dashboardService.getSprintSummary(uid(auth), projectId, sprintId));
+ return ResponseEntity.ok(dashboardService.getSprintSummary(uid(auth), projectId, sprintId, allSprints));
}
@GetMapping("/velocity")
@@ -42,19 +43,21 @@ public ResponseEntity> velocity(@PathVariable Long projec
}
@GetMapping("/efficiency")
- @Operation(summary = "Per-member efficiency — uses active sprint if sprintId is omitted")
+ @Operation(summary = "Per-member efficiency — uses active sprint if sprintId is omitted, all sprints if allSprints=true")
public ResponseEntity efficiency(@PathVariable Long projectId,
@RequestParam(required = false) Long sprintId,
+ @RequestParam(defaultValue = "false") boolean allSprints,
Authentication auth) {
- return ResponseEntity.ok(dashboardService.getEfficiency(uid(auth), projectId, sprintId));
+ return ResponseEntity.ok(dashboardService.getEfficiency(uid(auth), projectId, sprintId, allSprints));
}
@GetMapping("/workload")
- @Operation(summary = "Workload per member — uses active sprint if sprintId is omitted")
+ @Operation(summary = "Workload per member — uses active sprint if sprintId is omitted, all sprints if allSprints=true")
public ResponseEntity> workload(@PathVariable Long projectId,
@RequestParam(required = false) Long sprintId,
+ @RequestParam(defaultValue = "false") boolean allSprints,
Authentication auth) {
- return ResponseEntity.ok(dashboardService.getWorkload(uid(auth), projectId, sprintId));
+ return ResponseEntity.ok(dashboardService.getWorkload(uid(auth), projectId, sprintId, allSprints));
}
@GetMapping("/backlog")
diff --git a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/project/dashboard/DashboardService.java b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/project/dashboard/DashboardService.java
index 2cc6d7be8..da72d2eb9 100644
--- a/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/project/dashboard/DashboardService.java
+++ b/MtdrSpring/backend/src/main/java/com/springboot/MyTodoList/project/dashboard/DashboardService.java
@@ -32,9 +32,14 @@ public class DashboardService {
private final ProjectMemberRepository memberRepository;
@Transactional(readOnly = true)
- public SprintSummaryResponse getSprintSummary(Long userId, Long projectId, Long sprintId) {
+ public SprintSummaryResponse getSprintSummary(Long userId, Long projectId, Long sprintId, boolean allSprints) {
Project project = projectService.findProject(projectId);
projectService.requireParticipant(userId, project);
+
+ if (allSprints) {
+ return buildSprintSummaryForProject(project);
+ }
+
Sprint sprint = resolveSprint(project, sprintId);
Map statusCounts = new LinkedHashMap<>();
@@ -65,6 +70,35 @@ public SprintSummaryResponse getSprintSummary(Long userId, Long projectId, Long
.build();
}
+ private SprintSummaryResponse buildSprintSummaryForProject(Project project) {
+ Map statusCounts = new LinkedHashMap<>();
+ for (TaskStatus s : TaskStatus.values()) statusCounts.put(s.name(), 0);
+
+ long committed = 0;
+ long completed = 0;
+
+ for (Object[] row : taskRepository.findStatusCountsAndSpByProject(project)) {
+ TaskStatus status = (TaskStatus) row[0];
+ int count = ((Long) row[1]).intValue();
+ long sp = row[2] == null ? 0L : ((Number) row[2]).longValue();
+ statusCounts.put(status.name(), count);
+ committed += sp;
+ if (status == TaskStatus.DONE) completed = sp;
+ }
+
+ double pct = committed == 0 ? 0.0 : (double) completed / committed * 100.0;
+
+ return SprintSummaryResponse.builder()
+ .sprintId(null)
+ .sprintName("All Sprints")
+ .statusCounts(statusCounts)
+ .spCommitted(committed)
+ .spCompleted(completed)
+ .completionPercentage(pct)
+ .blockedCount(statusCounts.getOrDefault("BLOCKED", 0))
+ .build();
+ }
+
@Transactional(readOnly = true)
public List getVelocity(Long userId, Long projectId, int sprintCount) {
Project project = projectService.findProject(projectId);
@@ -85,9 +119,14 @@ public List getVelocity(Long userId, Long projectId, int sprin
}
@Transactional(readOnly = true)
- public EfficiencyResponse getEfficiency(Long userId, Long projectId, Long sprintId) {
+ public EfficiencyResponse getEfficiency(Long userId, Long projectId, Long sprintId, boolean allSprints) {
Project project = projectService.findProject(projectId);
projectService.requireParticipant(userId, project);
+
+ if (allSprints) {
+ return buildEfficiencyForProject(project);
+ }
+
Sprint sprint = resolveSprint(project, sprintId);
List members = new ArrayList<>();
@@ -107,17 +146,37 @@ public EfficiencyResponse getEfficiency(Long userId, Long projectId, Long sprint
return new EfficiencyResponse(sprint.getId(), sprint.getSprintName(), totalHours, totalSp, members);
}
+ private EfficiencyResponse buildEfficiencyForProject(Project project) {
+ List members = new ArrayList<>();
+ double totalHours = 0.0;
+ long totalSp = 0L;
+
+ for (Object[] row : taskRepository.findEfficiencyByProject(project, TaskStatus.DONE)) {
+ Long memberId = (Long) row[0];
+ String fullName = (String) row[1];
+ long sp = row[2] == null ? 0L : ((Number) row[2]).longValue();
+ double hours = row[3] == null ? 0.0 : ((BigDecimal) row[3]).doubleValue();
+ members.add(new EfficiencyResponse.MemberEfficiency(memberId, fullName, sp, hours));
+ totalSp += sp;
+ totalHours += hours;
+ }
+
+ return new EfficiencyResponse(null, "All Sprints", totalHours, totalSp, members);
+ }
+
@Transactional(readOnly = true)
- public List getWorkload(Long userId, Long projectId, Long sprintId) {
+ public List getWorkload(Long userId, Long projectId, Long sprintId, boolean allSprints) {
Project project = projectService.findProject(projectId);
projectService.requireParticipant(userId, project);
- Sprint sprint = resolveSprint(project, sprintId);
- // task counts and SP per user per status, from DB
Map> taskCountByUser = new HashMap<>();
Map> spByUser = new HashMap<>();
- for (Object[] row : taskRepository.findWorkloadBySprint(sprint)) {
+ List