Added more improvements and also fixed some issues
This commit is contained in:
parent
e5cc695fd9
commit
c8bc967399
Binary file not shown.
|
|
@ -3,6 +3,9 @@ package com.denniseckerskorn.controllers.class_management_controllers;
|
||||||
|
|
||||||
import com.denniseckerskorn.dtos.class_managment_dtos.AssistanceDTO;
|
import com.denniseckerskorn.dtos.class_managment_dtos.AssistanceDTO;
|
||||||
import com.denniseckerskorn.entities.class_managment.Assistance;
|
import com.denniseckerskorn.entities.class_managment.Assistance;
|
||||||
|
import com.denniseckerskorn.entities.class_managment.TrainingSession;
|
||||||
|
import com.denniseckerskorn.entities.user_managment.users.Student;
|
||||||
|
import com.denniseckerskorn.exceptions.BadRequestException;
|
||||||
import com.denniseckerskorn.services.class_managment_services.AssistanceService;
|
import com.denniseckerskorn.services.class_managment_services.AssistanceService;
|
||||||
import com.denniseckerskorn.services.class_managment_services.TrainingSessionService;
|
import com.denniseckerskorn.services.class_managment_services.TrainingSessionService;
|
||||||
import com.denniseckerskorn.services.user_managment_services.StudentService;
|
import com.denniseckerskorn.services.user_managment_services.StudentService;
|
||||||
|
|
@ -36,14 +39,20 @@ public class AssistanceController {
|
||||||
@Operation(summary = "Create a new assistance record")
|
@Operation(summary = "Create a new assistance record")
|
||||||
@PostMapping("/create")
|
@PostMapping("/create")
|
||||||
public ResponseEntity<AssistanceDTO> create(@RequestBody AssistanceDTO dto) {
|
public ResponseEntity<AssistanceDTO> create(@RequestBody AssistanceDTO dto) {
|
||||||
Assistance assistance = dto.toEntity(
|
Student student = studentService.findById(dto.getStudentId());
|
||||||
studentService.findById(dto.getStudentId()),
|
TrainingSession session = trainingSessionService.findById(dto.getSessionId());
|
||||||
trainingSessionService.findById(dto.getSessionId())
|
|
||||||
);
|
if (!student.getTrainingGroups().contains(session.getTrainingGroup())) {
|
||||||
|
throw new BadRequestException("The student does not belong to the selected training group.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Assistance assistance = dto.toEntity(student, session);
|
||||||
Assistance saved = assistanceService.save(assistance);
|
Assistance saved = assistanceService.save(assistance);
|
||||||
|
|
||||||
return ResponseEntity.ok(AssistanceDTO.fromEntity(saved));
|
return ResponseEntity.ok(AssistanceDTO.fromEntity(saved));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "Update an existing assistance record", description = "Update an existing assistance record")
|
@Operation(summary = "Update an existing assistance record", description = "Update an existing assistance record")
|
||||||
@PutMapping("/update")
|
@PutMapping("/update")
|
||||||
public ResponseEntity<AssistanceDTO> update(@RequestBody AssistanceDTO dto) {
|
public ResponseEntity<AssistanceDTO> update(@RequestBody AssistanceDTO dto) {
|
||||||
|
|
|
||||||
|
|
@ -60,16 +60,15 @@ public class TrainingGroupController {
|
||||||
group.setTeacher(teacher);
|
group.setTeacher(teacher);
|
||||||
TrainingGroup createdGroup = trainingGroupService.save(group);
|
TrainingGroup createdGroup = trainingGroupService.save(group);
|
||||||
|
|
||||||
TrainingSession trainingSession = new TrainingSession();
|
int months = dto.getRecurrenceMonths() != null && dto.getRecurrenceMonths() > 0
|
||||||
trainingSession.setTrainingGroup(createdGroup);
|
? dto.getRecurrenceMonths()
|
||||||
trainingSession.setDate(createdGroup.getSchedule());
|
: 1;
|
||||||
trainingSession.setStatus(StatusValues.ACTIVE);
|
trainingSessionService.generateRecurringSession(createdGroup, months);
|
||||||
trainingSessionService.save(trainingSession);
|
|
||||||
|
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(new TrainingGroupDTO(createdGroup));
|
return ResponseEntity.status(HttpStatus.CREATED).body(new TrainingGroupDTO(createdGroup));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "Assign a student to a group")
|
@Operation(summary = "Assign a student to a group")
|
||||||
@PutMapping("/assign-student")
|
@PutMapping("/assign-student")
|
||||||
public ResponseEntity<Void> assignStudent(@RequestParam Integer groupId, @RequestParam Integer studentId) {
|
public ResponseEntity<Void> assignStudent(@RequestParam Integer groupId, @RequestParam Integer studentId) {
|
||||||
|
|
@ -162,4 +161,20 @@ public class TrainingGroupController {
|
||||||
|
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "Generate recurring training sessions for a group", description = "Generates recurring training sessions for a specified number of months")
|
||||||
|
@Transactional
|
||||||
|
@PostMapping("/generate-recurring-sessions")
|
||||||
|
public ResponseEntity<TrainingGroupDTO> generateRecurringSessions(@RequestBody TrainingGroupDTO dto) {
|
||||||
|
TrainingGroup group = trainingGroupService.findById(dto.getId());
|
||||||
|
|
||||||
|
int months = (dto.getRecurrenceMonths() != null && dto.getRecurrenceMonths() > 0)
|
||||||
|
? dto.getRecurrenceMonths()
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
trainingSessionService.generateRecurringSession(group, months);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(new TrainingGroupDTO(group));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ public class TrainingGroupDTO {
|
||||||
private LocalDateTime schedule;
|
private LocalDateTime schedule;
|
||||||
private Integer teacherId;
|
private Integer teacherId;
|
||||||
private Set<Integer> studentIds = new HashSet<>();
|
private Set<Integer> studentIds = new HashSet<>();
|
||||||
|
private Integer recurrenceMonths;
|
||||||
|
|
||||||
public TrainingGroupDTO() {
|
public TrainingGroupDTO() {
|
||||||
}
|
}
|
||||||
|
|
@ -107,4 +108,12 @@ public class TrainingGroupDTO {
|
||||||
public void setStudentIds(Set<Integer> studentIds) {
|
public void setStudentIds(Set<Integer> studentIds) {
|
||||||
this.studentIds = studentIds;
|
this.studentIds = studentIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getRecurrenceMonths() {
|
||||||
|
return recurrenceMonths;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecurrenceMonths(Integer recurrenceMonths) {
|
||||||
|
this.recurrenceMonths = recurrenceMonths;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import com.denniseckerskorn.enums.StatusValues;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "MEMBERSHIPS")
|
@Table(name = "MEMBERSHIPS")
|
||||||
|
|
@ -29,8 +31,8 @@ public class Membership {
|
||||||
@Column(name = "status", nullable = false, length = 20)
|
@Column(name = "status", nullable = false, length = 20)
|
||||||
private StatusValues status;
|
private StatusValues status;
|
||||||
|
|
||||||
@OneToOne(mappedBy = "membership")
|
@OneToMany(mappedBy = "membership")
|
||||||
private Student student;
|
private Set<Student> students = new HashSet<>();
|
||||||
|
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return id;
|
return id;
|
||||||
|
|
@ -52,8 +54,8 @@ public class Membership {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Student getStudent() {
|
public Set<Student> getStudents() {
|
||||||
return student;
|
return students;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(Integer id) {
|
public void setId(Integer id) {
|
||||||
|
|
@ -76,8 +78,8 @@ public class Membership {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStudent(Student student) {
|
public void setStudents(Set<Student> students) {
|
||||||
this.student = student;
|
this.students = students;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -102,7 +104,6 @@ public class Membership {
|
||||||
", startDate=" + startDate +
|
", startDate=" + startDate +
|
||||||
", endDate=" + endDate +
|
", endDate=" + endDate +
|
||||||
", status=" + status +
|
", status=" + status +
|
||||||
", student=" + (student != null ? student.getId() : "null") +
|
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ public class Student {
|
||||||
@OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
private Set<StudentHistory> histories = new HashSet<>();
|
private Set<StudentHistory> histories = new HashSet<>();
|
||||||
|
|
||||||
@OneToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "fk_membership")
|
@JoinColumn(name = "fk_membership")
|
||||||
private Membership membership;
|
private Membership membership;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package com.denniseckerskorn.repositories.class_managment_repositories;
|
||||||
import com.denniseckerskorn.entities.class_managment.Membership;
|
import com.denniseckerskorn.entities.class_managment.Membership;
|
||||||
import com.denniseckerskorn.enums.MembershipTypeValues;
|
import com.denniseckerskorn.enums.MembershipTypeValues;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
|
@ -15,7 +17,8 @@ public interface MembershipRepository extends JpaRepository<Membership, Integer>
|
||||||
|
|
||||||
Membership findByStatus(String status);
|
Membership findByStatus(String status);
|
||||||
|
|
||||||
Membership findByStudentId(Integer studentId);
|
@Query("SELECT m FROM Membership m JOIN m.students s WHERE s.id = :studentId")
|
||||||
|
Membership findByStudentId(@Param("studentId") Integer studentId);
|
||||||
|
|
||||||
boolean existsByType(MembershipTypeValues type);
|
boolean existsByType(MembershipTypeValues type);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,13 +82,12 @@ public class MembershipService extends AbstractService<Membership, Integer> {
|
||||||
logger.info("Deleting membership by ID: {}", id);
|
logger.info("Deleting membership by ID: {}", id);
|
||||||
Membership membership = findById(id);
|
Membership membership = findById(id);
|
||||||
|
|
||||||
// Verificar si está asignado a un estudiante
|
if (!membership.getStudents().isEmpty()) {
|
||||||
Student student = membership.getStudent();
|
logger.error("Cannot delete membership. It is assigned to {} student(s)", membership.getStudents().size());
|
||||||
if (student != null) {
|
throw new InvalidDataException("Cannot delete membership because it is assigned to one or more students");
|
||||||
logger.error("Cannot delete membership. It is assigned to student ID {}", student.getId());
|
|
||||||
throw new InvalidDataException("Cannot delete membership because it is assigned to a student");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
super.deleteById(id);
|
super.deleteById(id);
|
||||||
logger.info("Membership with ID {} deleted", id);
|
logger.info("Membership with ID {} deleted", id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
|
@ -145,5 +144,4 @@ public class TrainingGroupService extends AbstractService<TrainingGroup, Integer
|
||||||
trainingGroupRepository.save(managedGroup);
|
trainingGroupRepository.save(managedGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.denniseckerskorn.services.class_managment_services;
|
package com.denniseckerskorn.services.class_managment_services;
|
||||||
|
|
||||||
|
import com.denniseckerskorn.entities.class_managment.TrainingGroup;
|
||||||
import com.denniseckerskorn.entities.class_managment.TrainingSession;
|
import com.denniseckerskorn.entities.class_managment.TrainingSession;
|
||||||
import com.denniseckerskorn.entities.user_managment.users.Student;
|
import com.denniseckerskorn.entities.user_managment.users.Student;
|
||||||
|
import com.denniseckerskorn.enums.StatusValues;
|
||||||
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
||||||
import com.denniseckerskorn.exceptions.EntityNotFoundException;
|
import com.denniseckerskorn.exceptions.EntityNotFoundException;
|
||||||
import com.denniseckerskorn.exceptions.InvalidDataException;
|
import com.denniseckerskorn.exceptions.InvalidDataException;
|
||||||
|
|
@ -107,13 +109,29 @@ public class TrainingSessionService extends AbstractService<TrainingSession, Int
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteAllAssistancesBySession(Integer sessionId) {
|
public void deleteAllAssistancesBySession(Integer sessionId) {
|
||||||
|
logger.info("Deleting all assistances for session ID: {}", sessionId);
|
||||||
TrainingSession session = findById(sessionId);
|
TrainingSession session = findById(sessionId);
|
||||||
|
|
||||||
if (session.getAssistances() != null && !session.getAssistances().isEmpty()) {
|
if (session.getAssistances() != null && !session.getAssistances().isEmpty()) {
|
||||||
session.getAssistances().clear();
|
session.getAssistances().clear();
|
||||||
update(session);
|
update(session);
|
||||||
}
|
}
|
||||||
|
logger.info("All assistances for session ID: {} have been deleted", sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void generateRecurringSession(TrainingGroup group, int months) {
|
||||||
|
logger.info("Generating recurring sessions for group: {} for {} months", group.getName(), months);
|
||||||
|
LocalDateTime baseDate = group.getSchedule();
|
||||||
|
|
||||||
|
for (int i = 0; i < months * 4; i++) {
|
||||||
|
LocalDateTime sessionDate = baseDate.plusWeeks(i);
|
||||||
|
TrainingSession newSession = new TrainingSession();
|
||||||
|
newSession.setDate(sessionDate);
|
||||||
|
newSession.setStatus(StatusValues.ACTIVE);
|
||||||
|
newSession.setTrainingGroup(group);
|
||||||
|
save(newSession);
|
||||||
|
logger.info("Created new session: {} for group: {}", newSession.getId(), group.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import "../styles/ContentArea.css";
|
||||||
const AssistanceForm = () => {
|
const AssistanceForm = () => {
|
||||||
const [students, setStudents] = useState([]);
|
const [students, setStudents] = useState([]);
|
||||||
const [sessions, setSessions] = useState([]);
|
const [sessions, setSessions] = useState([]);
|
||||||
|
const [filteredSessions, setFilteredSessions] = useState([]);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
studentId: "",
|
studentId: "",
|
||||||
sessionId: ""
|
sessionId: ""
|
||||||
|
|
@ -19,7 +20,21 @@ const AssistanceForm = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
const { name, value } = e.target;
|
||||||
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||||
|
|
||||||
|
if (name === "studentId") {
|
||||||
|
const student = students.find((s) => s.id === parseInt(value));
|
||||||
|
if (student) {
|
||||||
|
const groupIds = student.trainingGroups?.map((g) => g.id) || [];
|
||||||
|
const filtered = sessions.filter((s) =>
|
||||||
|
groupIds.includes(s.trainingGroupId)
|
||||||
|
);
|
||||||
|
setFilteredSessions(filtered);
|
||||||
|
} else {
|
||||||
|
setFilteredSessions([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
|
|
@ -27,17 +42,17 @@ const AssistanceForm = () => {
|
||||||
setErrorMsg("");
|
setErrorMsg("");
|
||||||
setSuccessMsg("");
|
setSuccessMsg("");
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
studentId: formData.studentId,
|
studentId: formData.studentId,
|
||||||
sessionId: formData.sessionId,
|
sessionId: formData.sessionId,
|
||||||
date: new Date(new Date().setHours(new Date().getHours() + 2)).toISOString()
|
date: new Date(new Date().setHours(new Date().getHours() + 2)).toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.post("/assistances/create", payload);
|
await api.post("/assistances/create", payload);
|
||||||
setSuccessMsg("✅ Asistencia registrada correctamente.");
|
setSuccessMsg("✅ Asistencia registrada correctamente.");
|
||||||
setFormData({ studentId: "", sessionId: "" });
|
setFormData({ studentId: "", sessionId: "" });
|
||||||
|
setFilteredSessions([]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
const msg = err.response?.data?.message || "❌ Error al registrar asistencia.";
|
const msg = err.response?.data?.message || "❌ Error al registrar asistencia.";
|
||||||
|
|
@ -48,14 +63,15 @@ const AssistanceForm = () => {
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Registrar Asistencia</h2>
|
<h2>Registrar Asistencia</h2>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit} className="form-column">
|
||||||
|
<label>Estudiante:</label>
|
||||||
<select
|
<select
|
||||||
name="studentId"
|
name="studentId"
|
||||||
value={formData.studentId}
|
value={formData.studentId}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<option value="">Selecciona estudiante</option>
|
<option value="">-- Selecciona estudiante --</option>
|
||||||
{students.map((s) => (
|
{students.map((s) => (
|
||||||
<option key={s.id} value={s.id}>
|
<option key={s.id} value={s.id}>
|
||||||
{s.user?.name} {s.user?.surname}
|
{s.user?.name} {s.user?.surname}
|
||||||
|
|
@ -63,21 +79,32 @@ const AssistanceForm = () => {
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select
|
<label>Sesión:</label>
|
||||||
name="sessionId"
|
{formData.studentId && filteredSessions.length === 0 ? (
|
||||||
value={formData.sessionId}
|
<p style={{ color: "#e74c3c", margin: "5px 0 15px" }}>
|
||||||
onChange={handleChange}
|
⚠️ Este estudiante no tiene sesiones disponibles según sus grupos asignados.
|
||||||
required
|
</p>
|
||||||
>
|
) : (
|
||||||
<option value="">Selecciona sesión</option>
|
<select
|
||||||
{sessions.map((s) => (
|
name="sessionId"
|
||||||
<option key={s.id} value={s.id}>
|
value={formData.sessionId}
|
||||||
{`${new Date(s.date).toLocaleString()} - Grupo: ${s.trainingGroup?.name || s.trainingGroupId}`}
|
onChange={handleChange}
|
||||||
</option>
|
required
|
||||||
))}
|
disabled={filteredSessions.length === 0}
|
||||||
</select>
|
>
|
||||||
|
<option value="">-- Selecciona sesión --</option>
|
||||||
|
{filteredSessions.map((s) => (
|
||||||
|
<option key={s.id} value={s.id}>
|
||||||
|
{`${new Date(s.date).toLocaleString()} - Grupo: ${s.trainingGroup?.name || s.trainingGroupId}`}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button type="submit" disabled={!formData.studentId || !formData.sessionId}>
|
||||||
|
Registrar
|
||||||
|
</button>
|
||||||
|
|
||||||
<button type="submit">Registrar</button>
|
|
||||||
<ErrorMessage message={successMsg} type="success" />
|
<ErrorMessage message={successMsg} type="success" />
|
||||||
<ErrorMessage message={errorMsg} type="error" />
|
<ErrorMessage message={errorMsg} type="error" />
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
import React, { use, useState } from "react";
|
|
||||||
import api from "../../api/axiosConfig";
|
|
||||||
import ErrorMessage from "../common/ErrorMessage";
|
|
||||||
import "../styles/ContentArea.css";
|
|
||||||
|
|
||||||
|
|
||||||
const MembershipForm = () => {
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
startDate: "",
|
|
||||||
endDate: "",
|
|
||||||
type: "BASIC",
|
|
||||||
status: "ACTIVE",
|
|
||||||
});
|
|
||||||
|
|
||||||
const [successMsg, setSuccessMsg] = useState("");
|
|
||||||
const [errorMsg, setErrorMsg] = useState("");
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setSuccessMsg("");
|
|
||||||
setErrorMsg("");
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log("Payload:", formData);
|
|
||||||
|
|
||||||
await api.post("/memberships/create", formData);
|
|
||||||
setSuccessMsg("✅ Membresía creada correctamente");
|
|
||||||
setFormData({
|
|
||||||
startDate: "",
|
|
||||||
endDate: "",
|
|
||||||
type: "BASIC",
|
|
||||||
status: "ACTIVE",
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
const msg =
|
|
||||||
err.response?.data?.message || "❌ Error al crear la membresía";
|
|
||||||
setErrorMsg(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="card">
|
|
||||||
<h2>Crear Membresía nueva</h2>
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<label>Fecha de inicio de la membresía:</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
name="startDate"
|
|
||||||
value={formData.startDate}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<label>Fecha fin de la membresía:</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
name="endDate"
|
|
||||||
value={formData.endDate}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
|
|
||||||
<label>Selecciona el tipo de membresía:</label>
|
|
||||||
<select
|
|
||||||
name="type"
|
|
||||||
value={formData.type}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="BASIC">Básico</option>
|
|
||||||
<option value="ADVANCED">Avanzado</option>
|
|
||||||
<option value="PREMIUM">Premium</option>
|
|
||||||
<option value="NO_LIMIT">Ilimitado</option>
|
|
||||||
<option value="TRIAL">Prueba</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<label>Estado de la membresía:</label>
|
|
||||||
<select
|
|
||||||
name="status"
|
|
||||||
value={formData.status}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="ACTIVE">Activo</option>
|
|
||||||
<option value="INACTIVE">Inactivo</option>
|
|
||||||
<option value="SUSPENDED">Suspendido / Expirado</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button type="submit">Crear nueva membresía</button>
|
|
||||||
</form>
|
|
||||||
<ErrorMessage type="success" message={successMsg} />
|
|
||||||
<ErrorMessage type="error" message={errorMsg} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MembershipForm;
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import api from "../../api/axiosConfig";
|
||||||
|
import ErrorMessage from "../common/ErrorMessage";
|
||||||
|
import "../styles/ContentArea.css";
|
||||||
|
|
||||||
|
const MembershipDetails = () => {
|
||||||
|
const [memberships, setMemberships] = useState([]);
|
||||||
|
const [errorMsg, setErrorMsg] = useState("");
|
||||||
|
const [successMsg, setSuccessMsg] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchMemberships();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchMemberships = async () => {
|
||||||
|
try {
|
||||||
|
const res = await api.get("/memberships/getAll");
|
||||||
|
setMemberships(res.data);
|
||||||
|
} catch (err) {
|
||||||
|
setErrorMsg("❌ Error al cargar las membresías");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (id, field, value) => {
|
||||||
|
setMemberships((prev) =>
|
||||||
|
prev.map((m) => (m.id === id ? { ...m, [field]: value } : m))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async (id) => {
|
||||||
|
const membership = memberships.find((m) => m.id === id);
|
||||||
|
if (!membership) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.put(`/memberships/update/${id}`, membership);
|
||||||
|
setSuccessMsg("✅ Membresía actualizada correctamente");
|
||||||
|
setErrorMsg("");
|
||||||
|
fetchMemberships();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setSuccessMsg("");
|
||||||
|
setErrorMsg("❌ Error al actualizar la membresía");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<h2>Detalles de Membresías</h2>
|
||||||
|
|
||||||
|
<ErrorMessage type="success" message={successMsg} />
|
||||||
|
<ErrorMessage type="error" message={errorMsg} />
|
||||||
|
|
||||||
|
<div className="table-wrapper">
|
||||||
|
<table className="styled-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Tipo</th>
|
||||||
|
<th>Inicio</th>
|
||||||
|
<th>Fin</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{memberships.map((m) => (
|
||||||
|
<tr key={m.id}>
|
||||||
|
<td>{m.id}</td>
|
||||||
|
<td>{m.type}</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={m.startDate}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(m.id, "startDate", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={m.endDate}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(m.id, "endDate", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
value={m.status}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(m.id, "status", e.target.value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="ACTIVE">Activo</option>
|
||||||
|
<option value="INACTIVE">Inactivo</option>
|
||||||
|
<option value="SUSPENDED">Suspendido / Expirado</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button onClick={() => handleUpdate(m.id)}>Actualizar</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MembershipDetails;
|
||||||
|
|
@ -8,6 +8,7 @@ const TrainingGroupForm = () => {
|
||||||
level: "",
|
level: "",
|
||||||
schedule: "",
|
schedule: "",
|
||||||
teacherId: "",
|
teacherId: "",
|
||||||
|
recurrenceMonths: 1, // nuevo campo
|
||||||
});
|
});
|
||||||
|
|
||||||
const [teachers, setTeachers] = useState([]);
|
const [teachers, setTeachers] = useState([]);
|
||||||
|
|
@ -36,9 +37,10 @@ const TrainingGroupForm = () => {
|
||||||
level: formData.level,
|
level: formData.level,
|
||||||
schedule: formData.schedule,
|
schedule: formData.schedule,
|
||||||
teacherId: parseInt(formData.teacherId),
|
teacherId: parseInt(formData.teacherId),
|
||||||
|
recurrenceMonths: parseInt(formData.recurrenceMonths), // incluir
|
||||||
});
|
});
|
||||||
setSuccessMsg("✅ Grupo de entrenamiento creado correctamente.");
|
setSuccessMsg("✅ Grupo de entrenamiento creado correctamente.");
|
||||||
setFormData({ name: "", level: "", schedule: "", teacherId: "" });
|
setFormData({ name: "", level: "", schedule: "", teacherId: "", recurrenceMonths: 1 });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error al crear grupo", err);
|
console.error("Error al crear grupo", err);
|
||||||
const backendMsg =
|
const backendMsg =
|
||||||
|
|
@ -80,6 +82,17 @@ const TrainingGroupForm = () => {
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<label>Meses de recurrencia:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="recurrenceMonths"
|
||||||
|
value={formData.recurrenceMonths}
|
||||||
|
onChange={handleChange}
|
||||||
|
min={1}
|
||||||
|
max={24}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
name="teacherId"
|
name="teacherId"
|
||||||
value={formData.teacherId}
|
value={formData.teacherId}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import AssistanceForm from '../forms/AssitanceForm';
|
||||||
import AssistanceList from '../lists/AssistanceList';
|
import AssistanceList from '../lists/AssistanceList';
|
||||||
import TrainingSessionList from '../lists/TrainingSessionList';
|
import TrainingSessionList from '../lists/TrainingSessionList';
|
||||||
import ViewTimetable from '../forms/ViewTimetable';
|
import ViewTimetable from '../forms/ViewTimetable';
|
||||||
import MembershipForm from '../forms/MembershipCreateForm';
|
import MembershipDetails from '../forms/MembershipDetails';
|
||||||
import MembershipList from '../lists/MembershipList';
|
import MembershipList from '../lists/MembershipList';
|
||||||
import InvoiceForm from '../forms/InvoiceForm';
|
import InvoiceForm from '../forms/InvoiceForm';
|
||||||
import InvoiceList from '../lists/InvoiceList';
|
import InvoiceList from '../lists/InvoiceList';
|
||||||
|
|
@ -60,7 +60,7 @@ const MainLayout = () => {
|
||||||
<Route path="/admin/class-management/assistance/list" element={<AssistanceList />} />
|
<Route path="/admin/class-management/assistance/list" element={<AssistanceList />} />
|
||||||
<Route path="/admin/class-management/training-session/list" element={<TrainingSessionList />} />
|
<Route path="/admin/class-management/training-session/list" element={<TrainingSessionList />} />
|
||||||
<Route path="/admin/class-management/training-groups/view-timetable" element={<ViewTimetable />} />
|
<Route path="/admin/class-management/training-groups/view-timetable" element={<ViewTimetable />} />
|
||||||
<Route path="/admin/class-management/memberships/create" element={<MembershipForm />} />
|
<Route path="/admin/class-management/memberships/details" element={<MembershipDetails />} />
|
||||||
<Route path="/admin/class-management/memberships/list" element={<MembershipList />} />
|
<Route path="/admin/class-management/memberships/list" element={<MembershipList />} />
|
||||||
|
|
||||||
{/* Finance */}
|
{/* Finance */}
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ const SidebarAdmin = () => {
|
||||||
<button onClick={() => navigate('/admin/class-management/training-session/list')}>📆 Ver Sesiones</button>
|
<button onClick={() => navigate('/admin/class-management/training-session/list')}>📆 Ver Sesiones</button>
|
||||||
<button onClick={() => navigate('/admin/class-management/assistance/create')}>📝 Registrar Asistencia</button>
|
<button onClick={() => navigate('/admin/class-management/assistance/create')}>📝 Registrar Asistencia</button>
|
||||||
<button onClick={() => navigate('/admin/class-management/assistance/list')}>📋 Ver Asistencias</button>
|
<button onClick={() => navigate('/admin/class-management/assistance/list')}>📋 Ver Asistencias</button>
|
||||||
<button onClick={() => navigate('/admin/class-management/memberships/create')}>➕ Crear Membresía</button>
|
<button onClick={() => navigate('/admin/class-management/memberships/details')}>➕ Detalles de las Membresías</button>
|
||||||
<button onClick={() => navigate('/admin/class-management/memberships/list')}>🏷️ Ver Membresías</button>
|
<button onClick={() => navigate('/admin/class-management/memberships/list')}>🏷️ Asignar Membresías</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="submenu">
|
<div className="submenu">
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,26 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import api from "../../api/axiosConfig";
|
import api from "../../api/axiosConfig";
|
||||||
import "../styles/ContentArea.css";
|
import "../styles/ContentArea.css";
|
||||||
|
import ErrorMessage from "../common/ErrorMessage";
|
||||||
|
|
||||||
const TrainingSessionList = () => {
|
const TrainingSessionList = () => {
|
||||||
const [sessions, setSessions] = useState([]);
|
const [sessions, setSessions] = useState([]);
|
||||||
|
const [groups, setGroups] = useState([]);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
const [successMsg, setSuccessMsg] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSessions();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchSessions = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get("/training-sessions/getAll");
|
const [sessionRes, groupRes] = await Promise.all([
|
||||||
setSessions(res.data);
|
api.get("/training-sessions/getAll"),
|
||||||
|
api.get("/training-groups/getAll")
|
||||||
|
]);
|
||||||
|
setSessions(sessionRes.data);
|
||||||
|
setGroups(groupRes.data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
setError("❌ Error al cargar las sesiones.");
|
setError("❌ Error al cargar las sesiones.");
|
||||||
|
|
@ -26,41 +33,67 @@ const TrainingSessionList = () => {
|
||||||
try {
|
try {
|
||||||
await api.delete(`/training-sessions/delete/${id}`);
|
await api.delete(`/training-sessions/delete/${id}`);
|
||||||
setSessions(sessions.filter((s) => s.id !== id));
|
setSessions(sessions.filter((s) => s.id !== id));
|
||||||
|
setSuccessMsg("✅ Sesión eliminada correctamente.");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
alert("❌ Error al eliminar la sesión.");
|
setError("❌ Error al eliminar la sesión.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGroupName = (id) => {
|
||||||
|
const group = groups.find((g) => g.id === id);
|
||||||
|
return group ? `${group.name} (${group.level})` : `Grupo ID ${id}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderStatus = (status) => {
|
||||||
|
const baseClass = "status-label";
|
||||||
|
switch (status) {
|
||||||
|
case "ACTIVE":
|
||||||
|
return <span className={`${baseClass} status-active`}>🟢 {status}</span>;
|
||||||
|
case "CANCELLED":
|
||||||
|
return <span className={`${baseClass} status-cancelled`}>🔴 {status}</span>;
|
||||||
|
case "FINISHED":
|
||||||
|
return <span className={`${baseClass} status-finished`}>⚪ {status}</span>;
|
||||||
|
default:
|
||||||
|
return <span className={baseClass}>{status}</span>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Sesiones de Entrenamiento</h2>
|
<h2>Sesiones de Entrenamiento</h2>
|
||||||
{error ? (
|
|
||||||
<p style={{ color: "red" }}>{error}</p>
|
<ErrorMessage message={error} type="error" />
|
||||||
) : (
|
<ErrorMessage message={successMsg} type="success" />
|
||||||
<div className="table-wrapper">
|
|
||||||
<table className="styled-table">
|
<div className="table-wrapper">
|
||||||
<thead>
|
<table className="styled-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Grupo</th>
|
||||||
|
<th>Fecha y Hora</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{sessions.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<td colSpan="5">No hay sesiones registradas.</td>
|
||||||
<th>Grupo</th>
|
|
||||||
<th>Fecha</th>
|
|
||||||
<th>Estado</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
) : (
|
||||||
<tbody>
|
sessions.map((s) => (
|
||||||
{sessions.map((s) => (
|
|
||||||
<tr key={s.id}>
|
<tr key={s.id}>
|
||||||
<td>{s.id}</td>
|
<td>{s.id}</td>
|
||||||
<td>{s.trainingGroupId}</td>
|
<td>{getGroupName(s.trainingGroupId)}</td>
|
||||||
<td>{new Date(s.date).toLocaleString()}</td>
|
<td>{new Date(s.date).toLocaleString()}</td>
|
||||||
<td>{s.status}</td>
|
<td>{renderStatus(s.status)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))
|
||||||
</tbody>
|
)}
|
||||||
</table>
|
</tbody>
|
||||||
</div>
|
</table>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,29 @@ form textarea:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-active {
|
||||||
|
background-color: #dff9fb;
|
||||||
|
color: #27ae60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-cancelled {
|
||||||
|
background-color: #fddede;
|
||||||
|
color: #e74c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-finished {
|
||||||
|
background-color: #ecf0f1;
|
||||||
|
color: #7f8c8d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue