From e5cc695fd9e272e0659dc610aa4f4e2ac7b93626 Mon Sep 17 00:00:00 2001 From: Dennis Eckerskorn Date: Fri, 23 May 2025 20:02:09 +0200 Subject: [PATCH] Improved style of several classes --- .../TrainingGroupController.java | 22 +- .../TrainingGroupDTO.java | 3 +- .../components/forms/TrainingGroupFrom.jsx | 52 +++-- .../forms/TrainingGroupsStudentManager.jsx | 63 +++--- .../src/components/forms/ViewTimetable.jsx | 68 ++++--- .../components/lists/StudentHistoryList.jsx | 129 ++++++++---- .../components/lists/TrainingGroupList.jsx | 191 ++++++++++++------ .../src/components/styles/ContentArea.css | 22 ++ .../src/components/styles/Timetable.css | 48 +++++ 9 files changed, 412 insertions(+), 186 deletions(-) create mode 100644 memberflow-frontend/src/components/styles/Timetable.css diff --git a/memberflow-api/src/main/java/com/denniseckerskorn/controllers/class_management_controllers/TrainingGroupController.java b/memberflow-api/src/main/java/com/denniseckerskorn/controllers/class_management_controllers/TrainingGroupController.java index d6e2099..a4f9011 100644 --- a/memberflow-api/src/main/java/com/denniseckerskorn/controllers/class_management_controllers/TrainingGroupController.java +++ b/memberflow-api/src/main/java/com/denniseckerskorn/controllers/class_management_controllers/TrainingGroupController.java @@ -20,6 +20,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -89,28 +90,31 @@ public class TrainingGroupController { return ResponseEntity.ok().build(); } - @Operation(summary = "Update an existing training group", description = "Update an existing training group with the specified ID") + @Operation(summary = "Actualizar un grupo de entrenamiento existente", description = "Actualiza un grupo de entrenamiento por su ID") @Transactional @PutMapping("/update/{id}") public ResponseEntity update(@PathVariable Integer id, @RequestBody TrainingGroupDTO dto) { dto.setId(id); + Teacher teacher = teacherService.findById(dto.getTeacherId()); - Set students = dto.getStudentIds().stream() + + Set studentIds = dto.getStudentIds() != null ? dto.getStudentIds() : Collections.emptySet(); + + Set students = studentIds.stream() .map(studentService::findById) .collect(Collectors.toSet()); - TrainingGroup updated = trainingGroupService.update(dto.toEntity(teacher, students)); + TrainingGroup updatedGroup = trainingGroupService.update(dto.toEntity(teacher, students)); - List sessions = updated.getTrainingSessions().stream().toList(); - if (!sessions.isEmpty()) { - TrainingSession session = sessions.get(0); - session.setDate(updated.getSchedule()); + updatedGroup.getTrainingSessions().stream().findFirst().ifPresent(session -> { + session.setDate(updatedGroup.getSchedule()); trainingSessionService.save(session); - } + }); - return ResponseEntity.ok(new TrainingGroupDTO(updated)); + return ResponseEntity.ok(new TrainingGroupDTO(updatedGroup)); } + @Operation(summary = "Find a training group by ID", description = "Retrieve a training group with the specified ID") @GetMapping("findById/{id}") public ResponseEntity findGroupById(@PathVariable Integer id) { diff --git a/memberflow-api/src/main/java/com/denniseckerskorn/dtos/class_managment_dtos/TrainingGroupDTO.java b/memberflow-api/src/main/java/com/denniseckerskorn/dtos/class_managment_dtos/TrainingGroupDTO.java index d76102b..b41d9b1 100644 --- a/memberflow-api/src/main/java/com/denniseckerskorn/dtos/class_managment_dtos/TrainingGroupDTO.java +++ b/memberflow-api/src/main/java/com/denniseckerskorn/dtos/class_managment_dtos/TrainingGroupDTO.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import java.time.LocalDateTime; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; @@ -18,7 +19,7 @@ public class TrainingGroupDTO { private String level; private LocalDateTime schedule; private Integer teacherId; - private Set studentIds; + private Set studentIds = new HashSet<>(); public TrainingGroupDTO() { } diff --git a/memberflow-frontend/src/components/forms/TrainingGroupFrom.jsx b/memberflow-frontend/src/components/forms/TrainingGroupFrom.jsx index 4e716d9..f230380 100644 --- a/memberflow-frontend/src/components/forms/TrainingGroupFrom.jsx +++ b/memberflow-frontend/src/components/forms/TrainingGroupFrom.jsx @@ -17,7 +17,7 @@ const TrainingGroupForm = () => { useEffect(() => { api.get("/teachers/getAll") .then((res) => setTeachers(res.data)) - .catch((err) => console.error("Error loading teachers", err)); + .catch((err) => console.error("Error al cargar profesores", err)); }, []); const handleChange = (e) => { @@ -37,7 +37,7 @@ const TrainingGroupForm = () => { schedule: formData.schedule, teacherId: parseInt(formData.teacherId), }); - setSuccessMsg("Training group created successfully."); + setSuccessMsg("✅ Grupo de entrenamiento creado correctamente."); setFormData({ name: "", level: "", schedule: "", teacherId: "" }); } catch (err) { console.error("Error al crear grupo", err); @@ -47,25 +47,46 @@ const TrainingGroupForm = () => { "❌ Error al crear el grupo. Verifica los datos."; setErrorMsg(backendMsg); } - }; return (
-

Create Training Group

-
- - +

Crear Grupo de Entrenamiento

+ + - - + - - + + - - + {teachers.map((teacher) => (
-
); }; diff --git a/memberflow-frontend/src/components/forms/TrainingGroupsStudentManager.jsx b/memberflow-frontend/src/components/forms/TrainingGroupsStudentManager.jsx index 715cd6a..5619254 100644 --- a/memberflow-frontend/src/components/forms/TrainingGroupsStudentManager.jsx +++ b/memberflow-frontend/src/components/forms/TrainingGroupsStudentManager.jsx @@ -27,9 +27,9 @@ const TrainingGroupStudentManager = () => { const group = await api.get(`/training-groups/findById/${groupId}`); setGroupStudents(group.data.studentIds || []); } catch (err) { - console.error("Error fetching group students", err); + console.error("Error al cargar alumnos del grupo", err); const msg = - err.response?.data?.message || "❌ Error al cargar datos del grupo."; + err.response?.data?.message || "❌ Error al cargar alumnos del grupo."; setErrorMsg(msg); setGroupStudents([]); } @@ -40,7 +40,7 @@ const TrainingGroupStudentManager = () => { await api.put(`/training-groups/assign-student`, null, { params: { groupId: selectedGroupId, - studentId: studentId, + studentId, }, }); setSuccessMsg("✅ Alumno asignado correctamente."); @@ -60,15 +60,16 @@ const TrainingGroupStudentManager = () => { await api.put(`/training-groups/remove-student`, null, { params: { groupId: selectedGroupId, - studentId: studentId, + studentId, }, }); - setSuccessMsg("Alumno eliminado del grupo."); + setSuccessMsg("✅ Alumno eliminado del grupo."); setErrorMsg(""); loadGroupStudents(selectedGroupId); } catch (err) { console.error(err); - const msg = err.response?.data?.message || "❌ Error al eliminar alumno."; + const msg = + err.response?.data?.message || "❌ Error al eliminar el alumno."; setErrorMsg(msg); setSuccessMsg(""); } @@ -77,11 +78,9 @@ const TrainingGroupStudentManager = () => { return (
-

Asignar Alumnos al Grupo de Entrenamiento

+

👥 Gestión de Alumnos por Grupo

- + + + + + + + + + + + + + + ) : ( + <> + + {h.student?.user?.name} {h.student?.user?.surname} + {(!h.student || !h.student.user) && `(ID: ${h.studentId})`} + + {h.eventDate} + {h.eventType} + {h.description} + + + + + + )} ))} diff --git a/memberflow-frontend/src/components/lists/TrainingGroupList.jsx b/memberflow-frontend/src/components/lists/TrainingGroupList.jsx index e0e1254..e7d9c7b 100644 --- a/memberflow-frontend/src/components/lists/TrainingGroupList.jsx +++ b/memberflow-frontend/src/components/lists/TrainingGroupList.jsx @@ -7,49 +7,93 @@ const TrainingGroupList = () => { const [groups, setGroups] = useState([]); const [teachers, setTeachers] = useState([]); const [editingGroupId, setEditingGroupId] = useState(null); - const [newTeacherId, setNewTeacherId] = useState(""); + const [editedGroup, setEditedGroup] = useState({}); + const [errorMsg, setErrorMsg] = useState(""); + const [successMsg, setSuccessMsg] = useState(""); useEffect(() => { fetchData(); }, []); const fetchData = async () => { - const groupRes = await api.get("/training-groups/getAll"); - const teacherRes = await api.get("/teachers/getAll"); - setGroups(groupRes.data); - setTeachers(teacherRes.data); - }; - - const handleUpdateTeacher = async (groupId) => { - const group = groups.find((g) => g.id === groupId); - const updatedGroup = { - ...group, - teacherId: parseInt(newTeacherId), - }; - - await api.put(`/training-groups/update/${groupId}`, updatedGroup); - setEditingGroupId(null); - fetchData(); - }; - - const handleDelete = async (groupId) => { - if (window.confirm("Are you sure you want to delete this group?")) { - await api.delete(`/training-groups/delete/${groupId}`); - fetchData(); + try { + const groupRes = await api.get("/training-groups/getAll"); + const teacherRes = await api.get("/teachers/getAll"); + setGroups(groupRes.data); + setTeachers(teacherRes.data); + } catch (err) { + setErrorMsg("❌ Error al cargar los grupos o profesores."); + console.error(err); } }; + const handleEditClick = (group) => { + setEditingGroupId(group.id); + setEditedGroup({ + name: group.name, + level: group.level, + schedule: group.schedule.slice(0, 16), + teacherId: group.teacherId, + }); + }; + + const handleChange = (e) => { + const { name, value } = e.target; + setEditedGroup((prev) => ({ ...prev, [name]: value })); + }; + + const handleUpdate = async (id) => { + try { + await api.put(`/training-groups/update/${id}`, { + ...editedGroup, + schedule: new Date(editedGroup.schedule).toISOString(), + teacherId: parseInt(editedGroup.teacherId), + studentIds: [] + }); + setSuccessMsg("✅ Grupo actualizado correctamente."); + setErrorMsg(""); + setEditingGroupId(null); + fetchData(); + } catch (err) { + setErrorMsg("❌ Error al actualizar el grupo."); + setSuccessMsg(""); + console.error(err); + } + }; + + const handleDelete = async (id) => { + if (!window.confirm("¿Estás seguro de que deseas eliminar este grupo?")) return; + + try { + await api.delete(`/training-groups/delete/${id}`); + setSuccessMsg("✅ Grupo eliminado correctamente."); + fetchData(); + } catch (err) { + setErrorMsg("❌ Error al eliminar el grupo."); + console.error(err); + } + }; + + const getTeacherName = (id) => { + const teacher = teachers.find((t) => t.id === id); + return teacher ? `${teacher.user?.name} ${teacher.user?.surname}` : `(ID ${id})`; + }; + return (
-

Grupos de entrenamiento

+

Grupos de Entrenamiento

+ + + +
- + - + @@ -57,44 +101,63 @@ const TrainingGroupList = () => { {groups.map((group) => ( - - - - - - + {editingGroupId === group.id ? ( + <> + + + + + + + + ) : ( + <> + + + + + + + + )} ))} diff --git a/memberflow-frontend/src/components/styles/ContentArea.css b/memberflow-frontend/src/components/styles/ContentArea.css index 3026abb..e91a770 100644 --- a/memberflow-frontend/src/components/styles/ContentArea.css +++ b/memberflow-frontend/src/components/styles/ContentArea.css @@ -264,6 +264,28 @@ form textarea:focus { outline: none; } +.group-student-list li { + padding: 10px 12px; + margin-bottom: 6px; + background-color: #ffffff; + border: 1px solid #dcdde1; + border-radius: 10px; + display: flex; + justify-content: space-between; + align-items: center; + transition: background-color 0.2s; +} + +.group-student-list li:hover { + background-color: #f0f4f8; +} + +.group-student-list li .delete-button, +.group-student-list li .add-button { + margin-left: 10px; +} + + diff --git a/memberflow-frontend/src/components/styles/Timetable.css b/memberflow-frontend/src/components/styles/Timetable.css new file mode 100644 index 0000000..3f51ea4 --- /dev/null +++ b/memberflow-frontend/src/components/styles/Timetable.css @@ -0,0 +1,48 @@ +/* Ajuste general de la tabla */ +.timetable-wrapper { + overflow-x: auto; +} + +.timetable-table { + width: 100%; + border-collapse: collapse; + table-layout: fixed; /* fuerza a que todas las celdas tengan el mismo ancho */ +} + +.timetable-table th, +.timetable-table td { + padding: 6px; + text-align: center; + border: 1px solid #dcdde1; + height: 60px; /* ajusta esto si lo quieres más compacto */ + vertical-align: middle; + position: relative; +} + +/* Hora a la izquierda */ +.hour-label { + font-weight: bold; + background-color: #f0f0f0; + width: 70px; +} + +/* Celda del día */ +.day-cell { + padding: 0; +} + +/* Tarjeta dentro de la celda */ +.timetable-block { + background-color: #2ecc71; + color: white; + width: 100%; + height: 100%; + font-size: 14px; + padding: 4px; + border-radius: 0; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +}
ID GrupoID Nombre NivelFecha / HoraHorario Profesor Acciones
{group.id}{group.name}{group.level}{new Date(group.schedule).toLocaleString()} - {editingGroupId === group.id ? ( - - ) : ( - `${group.teacherId}` - )} - - {editingGroupId === group.id ? ( - - ) : ( - - )} - - {group.id} + + + + + + + + + + + {group.id}{group.name}{group.level}{new Date(group.schedule).toLocaleString()}{getTeacherName(group.teacherId)} + + +