Skip to content

Commit b37e7bc

Browse files
committed
feat(frontend): editar y eliminar sprint, exportar tareas y eliminar tarea en backlog
1 parent cd2c4c6 commit b37e7bc

3 files changed

Lines changed: 125 additions & 8 deletions

File tree

MtdrSpring/backend/src/main/frontend/src/pages/BacklogPage.jsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { taskService } from '../services/taskService';
55
import { sprintService } from '../services/sprintService';
66
import TaskTable from '../components/tasks/TaskTable';
77
import Modal from '../components/common/Modal';
8+
import ConfirmDialog from '../components/common/ConfirmDialog';
89
import Badge from '../components/common/Badge';
10+
import Button from '../components/common/Button';
911
import LoadingSpinner from '../components/common/LoadingSpinner';
1012

1113
export default function BacklogPage() {
@@ -17,13 +19,15 @@ export default function BacklogPage() {
1719
const [selectedTask, setSelectedTask] = useState(null);
1820
const [moving, setMoving] = useState(false);
1921
const [error, setError] = useState('');
22+
const [confirmDelete, setConfirmDelete] = useState(null);
23+
const [deleting, setDeleting] = useState(false);
2024

2125
const isManager = userRole === 'MANAGER';
2226

2327
const load = () => {
2428
setLoading(true);
2529
Promise.all([
26-
taskService.list(project.id, {}),
30+
taskService.list(project.id, { size: 500 }),
2731
sprintService.list(project.id),
2832
])
2933
.then(([taskData, sprintData]) => {
@@ -36,6 +40,20 @@ export default function BacklogPage() {
3640

3741
useEffect(load, [project.id]);
3842

43+
const doDelete = async () => {
44+
setDeleting(true);
45+
try {
46+
await taskService.delete(project.id, confirmDelete.id);
47+
setConfirmDelete(null);
48+
setSelectedTask(null);
49+
load();
50+
} catch (err) {
51+
toast.error(err.message);
52+
} finally {
53+
setDeleting(false);
54+
}
55+
};
56+
3957
const handleMoveToSprint = async (sprintId) => {
4058
setMoving(true);
4159
try {
@@ -99,9 +117,27 @@ export default function BacklogPage() {
99117
</div>
100118
</div>
101119
)}
120+
121+
{isManager && (
122+
<div className="pt-2 border-t border-gray-100">
123+
<Button variant="danger" onClick={() => setConfirmDelete(selectedTask)}>
124+
Eliminar tarea
125+
</Button>
126+
</div>
127+
)}
102128
</div>
103129
)}
104130
</Modal>
131+
<ConfirmDialog
132+
open={!!confirmDelete}
133+
onClose={() => setConfirmDelete(null)}
134+
onConfirm={doDelete}
135+
title="Eliminar tarea"
136+
message={`"${confirmDelete?.taskName}" será eliminada permanentemente. Esta acción no se puede deshacer.`}
137+
confirmLabel="Eliminar"
138+
variant="danger"
139+
loading={deleting}
140+
/>
105141
</div>
106142
);
107143
}

MtdrSpring/backend/src/main/frontend/src/pages/SprintsPage.jsx

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useProject } from '../context/ProjectContext';
33
import { useToast } from '../context/ToastContext';
44
import { sprintService } from '../services/sprintService';
55
import { taskService } from '../services/taskService';
6+
import { exportTasksToJson } from '../services/importExportService';
67
import SprintList from '../components/sprints/SprintList';
78
import SprintForm from '../components/sprints/SprintForm';
89
import TaskTable from '../components/tasks/TaskTable';
@@ -36,6 +37,10 @@ export default function SprintsPage() {
3637
const [confirmDeleteTask, setConfirmDeleteTask] = useState(null);
3738
const [actionLoading, setActionLoading] = useState(false);
3839

40+
const [editSprint, setEditSprint] = useState(null);
41+
const [sprintSaving, setSprintSaving] = useState(false);
42+
const [confirmDeleteSprint, setConfirmDeleteSprint] = useState(null);
43+
3944
const isManager = userRole === 'MANAGER';
4045

4146
const load = () => {
@@ -110,6 +115,29 @@ export default function SprintsPage() {
110115
finally { setActionLoading(false); }
111116
};
112117

118+
const handleSprintUpdate = async (form) => {
119+
setSprintSaving(true);
120+
try {
121+
const updated = await sprintService.update(project.id, editSprint.id, form);
122+
setEditSprint(null);
123+
setSelectedSprint(updated);
124+
load();
125+
} catch (err) { toast.error(err.message); }
126+
finally { setSprintSaving(false); }
127+
};
128+
129+
const doDeleteSprint = async () => {
130+
setActionLoading(true);
131+
try {
132+
await sprintService.delete(project.id, confirmDeleteSprint.id);
133+
setConfirmDeleteSprint(null);
134+
setSelectedSprint(null);
135+
setSprintTasks([]);
136+
load();
137+
} catch (err) { toast.error(err.message); }
138+
finally { setActionLoading(false); }
139+
};
140+
113141
const handleStatusChange = async (task, status) => {
114142
try {
115143
const updated = await taskService.changeStatus(project.id, task.id, status);
@@ -195,16 +223,43 @@ export default function SprintsPage() {
195223
>
196224
{selectedSprint && (
197225
<div className="space-y-4">
198-
<div className="flex items-center gap-3">
199-
<Badge value={selectedSprint.status} />
200-
<span className="text-xs text-gray-400">
201-
{selectedSprint.startDate}{selectedSprint.endDate}
202-
</span>
203-
{selectedSprint.goal && (
204-
<span className="text-xs text-gray-500 italic">{selectedSprint.goal}</span>
226+
<div className="flex items-center justify-between gap-3">
227+
<div className="flex items-center gap-3 min-w-0">
228+
<Badge value={selectedSprint.status} />
229+
<span className="text-xs text-gray-400 shrink-0">
230+
{selectedSprint.startDate}{selectedSprint.endDate}
231+
</span>
232+
{selectedSprint.goal && (
233+
<span className="text-xs text-gray-500 italic truncate">{selectedSprint.goal}</span>
234+
)}
235+
</div>
236+
{isManager && (
237+
<div className="flex items-center gap-2 shrink-0">
238+
{selectedSprint.status !== 'CLOSED' && (
239+
<Button variant="secondary" onClick={() => setEditSprint(selectedSprint)}>
240+
Editar
241+
</Button>
242+
)}
243+
{selectedSprint.status !== 'ACTIVE' && (
244+
<Button variant="danger" onClick={() => setConfirmDeleteSprint(selectedSprint)}>
245+
Eliminar
246+
</Button>
247+
)}
248+
</div>
205249
)}
206250
</div>
207251

252+
{!tasksLoading && sprintTasks.length > 0 && (
253+
<div className="flex justify-end">
254+
<Button
255+
variant="secondary"
256+
onClick={() => exportTasksToJson(sprintTasks, selectedSprint.sprintName)}
257+
>
258+
Exportar tareas
259+
</Button>
260+
</div>
261+
)}
262+
208263
{tasksLoading ? (
209264
<LoadingSpinner />
210265
) : sprintTasks.length === 0 ? (
@@ -285,6 +340,29 @@ export default function SprintsPage() {
285340
variant="danger"
286341
loading={actionLoading}
287342
/>
343+
344+
{/* Edit sprint modal */}
345+
<Modal open={!!editSprint} onClose={() => setEditSprint(null)} title="Edit sprint">
346+
{editSprint && (
347+
<SprintForm
348+
initial={editSprint}
349+
onSubmit={handleSprintUpdate}
350+
onCancel={() => setEditSprint(null)}
351+
loading={sprintSaving}
352+
/>
353+
)}
354+
</Modal>
355+
356+
<ConfirmDialog
357+
open={!!confirmDeleteSprint}
358+
onClose={() => setConfirmDeleteSprint(null)}
359+
onConfirm={doDeleteSprint}
360+
title="Eliminar sprint"
361+
message={`¿Eliminar "${confirmDeleteSprint?.sprintName}"? Las tareas del sprint se moverán al backlog. Esta acción no se puede deshacer.`}
362+
confirmLabel="Eliminar"
363+
variant="danger"
364+
loading={actionLoading}
365+
/>
288366
</div>
289367
);
290368
}

MtdrSpring/backend/src/main/frontend/src/services/sprintService.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ export const sprintService = {
2222

2323
reopen: (projectId, sprintId) =>
2424
apiFetch(`/projects/${projectId}/sprints/${sprintId}/reopen`, { method: 'POST' }),
25+
26+
delete: (projectId, sprintId) =>
27+
apiFetch(`/projects/${projectId}/sprints/${sprintId}`, { method: 'DELETE' }),
2528
};

0 commit comments

Comments
 (0)