Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/com/classes/controllers/ClassController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.classes.dtos.Class.ClassRequest;
import com.classes.dtos.Class.ClassResponse;
import com.classes.dtos.Class.MonthlyCalendarDTO;
import com.classes.services.ClassService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down Expand Up @@ -65,4 +66,15 @@ public ResponseEntity<String> deleteClass(@PathVariable UUID id) {
classService.deleteClass(id);
return ResponseEntity.ok("Clase eliminada correctamente");
}

@Operation(summary = "Ver calendario mensual", description = "Vista de calendario por mes con clases agrupadas por fecha")
@GetMapping("/calendar/monthly")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<MonthlyCalendarDTO> getMonthlyCalendar(
@RequestParam int year,
@RequestParam int month) {
log.info("📅 Admin consultando calendario mensual para {}/{}", month, year);
MonthlyCalendarDTO calendar = classService.getMonthlyCalendar(year, month);
return ResponseEntity.ok(calendar);
}
}
24 changes: 19 additions & 5 deletions src/main/java/com/classes/controllers/ClassViewController.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.classes.controllers;

import com.classes.dtos.Class.*;
import com.classes.dtos.Class.MonthlyCalendarDTO;
import com.classes.services.AuthorizationService;
import com.classes.services.ClassService;
import com.classes.services.ClassStatsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
Expand All @@ -26,6 +28,7 @@
public class ClassViewController {

private final ClassService classService;
private final ClassStatsService classStatsService;
private final AuthorizationService authorizationService;

@Operation(summary = "Listar todas las clases", description = "Todos los roles pueden ver la lista")
Expand All @@ -42,7 +45,7 @@ public ResponseEntity<List<ClassResponse>> findAllClasses() {
public ResponseEntity<List<ClassWithStatsResponse>> getMyClassesWithStats(Authentication authentication) {
UUID trainerId = authorizationService.getUserId(authentication);
log.info("Trainer {} consultando sus clases con estadísticas", trainerId);
List<ClassWithStatsResponse> classes = classService.getClassesWithStatsByTrainer(trainerId);
List<ClassWithStatsResponse> classes = classStatsService.getClassesWithStatsByTrainer(trainerId);
return ResponseEntity.ok(classes);
}

Expand All @@ -51,7 +54,7 @@ public ResponseEntity<List<ClassWithStatsResponse>> getMyClassesWithStats(Authen
@PreAuthorize("hasRole('TRAINER') or hasRole('ADMIN')")
public ResponseEntity<ClassDetailResponse> getClassDetail(@PathVariable UUID id) {
log.info("Consultando detalle de la clase {}", id);
ClassDetailResponse detail = classService.getClassDetail(id);
ClassDetailResponse detail = classStatsService.getClassDetail(id);
return ResponseEntity.ok(detail);
}

Expand All @@ -65,10 +68,10 @@ public ResponseEntity<List<CalendarClassDTO>> getClassesForCalendar(
log.info("📅 Consultando clases para calendario");

if (startDate != null && endDate != null) {
List<CalendarClassDTO> classes = classService.getClassesForCalendar(startDate, endDate);
List<CalendarClassDTO> classes = classStatsService.getClassesForCalendar(startDate, endDate);
return ResponseEntity.ok(classes);
} else {
List<CalendarClassDTO> classes = classService.getUpcomingClasses();
List<CalendarClassDTO> classes = classStatsService.getUpcomingClasses();
return ResponseEntity.ok(classes);
}
}
Expand All @@ -78,7 +81,18 @@ public ResponseEntity<List<CalendarClassDTO>> getClassesForCalendar(
@PreAuthorize("hasRole('USER') or hasRole('TRAINER') or hasRole('ADMIN')")
public ResponseEntity<List<CalendarClassDTO>> getUpcomingClasses() {
log.info("Consultando próximas clases");
List<CalendarClassDTO> classes = classService.getUpcomingClasses();
List<CalendarClassDTO> classes = classStatsService.getUpcomingClasses();
return ResponseEntity.ok(classes);
}

@Operation(summary = "Ver calendario mensual", description = "Vista de calendario por mes con clases agrupadas por fecha")
@GetMapping("/calendar/monthly")
@PreAuthorize("hasRole('USER') or hasRole('TRAINER') or hasRole('ADMIN')")
public ResponseEntity<MonthlyCalendarDTO> getMonthlyCalendar(
@RequestParam int year,
@RequestParam int month) {
log.info("📅 Consultando calendario mensual para {}/{}", month, year);
MonthlyCalendarDTO calendar = classService.getMonthlyCalendar(year, month);
return ResponseEntity.ok(calendar);
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/classes/entities/TrainerEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
@EntityListeners(AuditListener.class)
public class TrainerEntity {
@Id
// private UUID userid;
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String firstName;
private String lastName;
Expand Down
12 changes: 3 additions & 9 deletions src/main/java/com/classes/services/ClassService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.springframework.data.domain.Page;
import com.classes.dtos.Class.ClassRequest;
import com.classes.dtos.Class.ClassResponse;
import com.classes.dtos.Class.MonthlyCalendarDTO;

import java.time.LocalDate;
import java.util.List;
Expand All @@ -21,13 +22,6 @@ public interface ClassService {
ClassResponse updateClass(UUID id, ClassRequest request);

void deleteClass(UUID id);


List<ClassWithStatsResponse> getClassesWithStatsByTrainer(UUID trainerId);

ClassDetailResponse getClassDetail(UUID classId);

List<CalendarClassDTO> getClassesForCalendar(LocalDate startDate, LocalDate endDate);

List<CalendarClassDTO> getUpcomingClasses();

MonthlyCalendarDTO getMonthlyCalendar(int year, int month);
}
175 changes: 26 additions & 149 deletions src/main/java/com/classes/services/Impl/ClassServiceImpl.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.classes.services.Impl;

import com.classes.services.ClassStatsService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand All @@ -24,8 +25,9 @@
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import java.time.YearMonth;
import java.time.format.TextStyle;
import java.util.*;
import java.util.stream.Collectors;


Expand All @@ -36,11 +38,9 @@ public class ClassServiceImpl implements ClassService {

private final ClassRepository repository;
private final ClassMapper classMapper;
private final ClassStatsMapper classStatsMapper;
private final TrainerRepository trainerRepository;
private final LocationRepository locationRepository;
private final ClassReservationRepository reservationRepository;
private final MemberClientService memberClientService;
private final ClassStatsService classStatsService;


@Transactional
Expand Down Expand Up @@ -121,152 +121,29 @@ private void validateTrainerAndLocation(ClassRequest request) {
}
}

//<---ESTADISTICAS---->

@Override
@Transactional(readOnly = true)
public List<ClassWithStatsResponse> getClassesWithStatsByTrainer(UUID trainerId) {
log.info("📊 Obteniendo clases con estadísticas para el trainer {}", trainerId);
List<ClassEntity> classes = repository.findByTrainerId(trainerId);
return classes.stream().map(classEntity -> {
ClassWithStatsResponse response = classStatsMapper.toClassWithStatsResponse(classEntity);
long currentStudents = reservationRepository.countByClassEntityIdAndStatus(
classEntity.getId(), ReservationStatus.RESERVADO);
response.setCurrentStudents((int) currentStudents);
Double avgAttendance = reservationRepository.calculateAverageAttendanceByClassId(classEntity.getId());
response.setAverageAttendance(avgAttendance != null ? avgAttendance : 0.0);
response.setStatus(determineClassStatus(classEntity, (int) currentStudents));

return response;
}).collect(Collectors.toList());
}

@Override
@Transactional(readOnly = true)
public ClassDetailResponse getClassDetail(UUID classId) {
log.info("🔍 Obteniendo detalle de la clase {}", classId);

ClassEntity classEntity = repository.findById(classId)
.orElseThrow(() -> new IllegalArgumentException("La clase con ID " + classId + " no existe"));
ClassDetailResponse response = classStatsMapper.toClassDetailResponse(classEntity);
List<ClassReservation> reservations = reservationRepository.findByClassEntityId(classId);
List<UUID> memberIds = reservations.stream()
.map(ClassReservation::getMemberId)
.distinct()
.collect(Collectors.toList());
List<MemberInfoDTO> membersInfo = memberClientService.getMembersInfo(memberIds);
List<StudentInClassDTO> students = reservations.stream()
.map(reservation -> buildStudentDTO(reservation, membersInfo))
.collect(Collectors.toList());

response.setCurrentStudents(students.size());
response.setStudents(students);

return response;
}

private StudentInClassDTO buildStudentDTO(ClassReservation reservation, List<MemberInfoDTO> membersInfo) {
MemberInfoDTO memberInfo = membersInfo.stream()
.filter(m -> m.getId().equals(reservation.getMemberId()))
.findFirst()
.orElse(createDefaultMemberInfo(reservation.getMemberId()));

List<ClassReservation> memberReservations =
reservationRepository.findByMemberId(reservation.getMemberId());

long totalClasses = memberReservations.size();

// ✅ Cambiado: uso de Boolean.TRUE.equals() para evitar errores por null
long attendedClasses = memberReservations.stream()
.filter(r -> Boolean.TRUE.equals(r.getAttended()))
.count();

double attendancePercentage = totalClasses > 0
? (attendedClasses * 100.0 / totalClasses) : 0.0;

String initials = getInitials(memberInfo.getFirstName(), memberInfo.getLastName());

return StudentInClassDTO.builder()
.memberId(reservation.getMemberId())
.name(memberInfo.getFirstName() + " " + memberInfo.getLastName())
.email(memberInfo.getEmail())
.avatarInitials(initials)
.status(memberInfo.getStatus())
.membershipType(memberInfo.getMembershipType())
.attendancePercentage(attendancePercentage)
.totalClasses((int) totalClasses)
.lastAccess(memberInfo.getLastAccess())
.reservationId(reservation.getId())
public MonthlyCalendarDTO getMonthlyCalendar(int year, int month) {
log.info("📅 Obteniendo calendario mensual para {}/{}", month, year);
YearMonth yearMonth = YearMonth.of(year, month);
LocalDate firstDay = yearMonth.atDay(1);
LocalDate lastDay = yearMonth.atEndOfMonth();
List<CalendarClassDTO> allClasses = classStatsService.getClassesForCalendar(firstDay, lastDay);
Map<LocalDate, List<CalendarClassDTO>> classesByDate = allClasses.stream()
.collect(Collectors.groupingBy(CalendarClassDTO::getClassDate));
List<LocalDate> daysWithClasses = new ArrayList<>(classesByDate.keySet());
Collections.sort(daysWithClasses);
String monthName = yearMonth.getMonth().getDisplayName(TextStyle.FULL, new java.util.Locale("es", "ES"));
monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1);
return MonthlyCalendarDTO.builder()
.year(year)
.month(month)
.monthName(monthName)
.firstDayOfMonth(firstDay)
.lastDayOfMonth(lastDay)
.classesByDate(classesByDate)
.totalClasses(allClasses.size())
.daysWithClasses(daysWithClasses)
.build();
}

@Override
@Transactional(readOnly = true)
public List<CalendarClassDTO> getClassesForCalendar(LocalDate startDate, LocalDate endDate) {
log.info("Obteniendo clases para calendario entre {} y {}", startDate, endDate);

List<ClassEntity> classes = repository.findByClassDateBetween(startDate, endDate);
return mapToCalendarDTOs(classes);
}

@Override
@Transactional(readOnly = true)
public List<CalendarClassDTO> getUpcomingClasses() {
log.info("Obteniendo próximas clases");

List<ClassEntity> classes = repository.findUpcomingClasses(LocalDate.now());
return mapToCalendarDTOs(classes);
}
private List<CalendarClassDTO> mapToCalendarDTOs(List<ClassEntity> classes) {
return classes.stream().map(classEntity -> {
CalendarClassDTO dto = classStatsMapper.toCalendarDTO(classEntity);
long currentStudents = reservationRepository.countByClassEntityIdAndStatus(
classEntity.getId(), ReservationStatus.RESERVADO);
dto.setCurrentStudents((int) currentStudents);
String action = determineAction(classEntity, (int) currentStudents);
dto.setAction(action);
return dto;
}).collect(Collectors.toList());
}

private String determineClassStatus(ClassEntity classEntity, int currentStudents) {
if (!classEntity.isActive()) {
return "Cancelada";
}
if (currentStudents >= classEntity.getMaxCapacity()) {
return "Llena";
}
return "Activa";
}

private String determineAction(ClassEntity classEntity, int currentStudents) {
if (!classEntity.isActive()) {
return "Cancelada";
}
if (currentStudents >= classEntity.getMaxCapacity()) {
return "Llena";
}
if (currentStudents >= classEntity.getMaxCapacity() * 0.9) {
return "Lista de espera";
}
return "Reservar";
}


private MemberInfoDTO createDefaultMemberInfo(UUID memberId) {
return MemberInfoDTO.builder()
.id(memberId)
.firstName("Usuario")
.lastName("Desconocido")
.email("no-disponible@email.com")
.status("DESCONOCIDO")
.membershipType("N/A")
.build();
}

private String getInitials(String firstName, String lastName) {
String first = firstName != null && !firstName.isEmpty() ? firstName.substring(0, 1) : "";
String last = lastName != null && !lastName.isEmpty() ? lastName.substring(0, 1) : "";
return (first + last).toUpperCase();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class CloudinaryServiceImpl implements CloudinaryService {

private final Cloudinary cloudinary;

private static final String TRAINERS_FOLDER = "fitdesk/classes/profile";
private static final String TRAINERS_FOLDER = "fitdesk/classes/trainerprofile";
private static final Set<String> ALLOWED_EXTENSIONS = Set.of("jpg","jpeg","png","webp");
private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB

Expand Down
Loading