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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import backend.pirocheck.Assignment.dto.request.AssignmentCreateReq;
import backend.pirocheck.Assignment.dto.request.AssignmentItemCreateReq;
import backend.pirocheck.Assignment.dto.request.AssignmentItemUpdateReq;
import backend.pirocheck.Assignment.dto.request.AssignmentUpdateReq;
import backend.pirocheck.Assignment.dto.response.AssignmentWeekRes;
import backend.pirocheck.Assignment.entity.AssignmentStatus;
Expand Down Expand Up @@ -119,13 +120,14 @@ public AssignmentStatus submissionAssignment(
}
)
@PutMapping("/admin/users/{userId}/assignments/{assignmentId}/submission")
public String updateSubmission(
public AssignmentStatus updateSubmission(
@Parameter(description = "사용자 ID", example = "1")
@PathVariable Long userId,
@Parameter(description = "과제 ID", example = "1")
@PathVariable Long assignmentId
@PathVariable Long assignmentId,
@RequestBody AssignmentItemUpdateReq req
) {
return null;
return assignmentService.updateAssignmentItem(userId, assignmentId, req);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package backend.pirocheck.Assignment.dto.request;

import backend.pirocheck.Assignment.entity.AssignmentStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class AssignmentItemUpdateReq {

@Pattern(regexp = "SUCCESS/INSUFFICIENT/FAILURE", message = "status는 SUCCESS, INSUFFICIENT 혹은 FAILURE 여야 합니다.")
@Schema(description = "과제 결과", example = "SUCCESS")
private AssignmentStatus status;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,10 @@ public static AssignmentItem create(User user, Assignment assignment, Assignment
.submitted(submitted)
.build();
}

public void update(AssignmentStatus submitted) {

this.submitted = submitted;

}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package backend.pirocheck.Assignment.repository;

import backend.pirocheck.Assignment.entity.Assignment;
import backend.pirocheck.Assignment.entity.AssignmentItem;
import backend.pirocheck.Assignment.entity.AssignmentStatus;
import backend.pirocheck.User.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface AssignmentItemRepository extends JpaRepository<AssignmentItem, Long> {
Expand All @@ -17,4 +19,7 @@ public interface AssignmentItemRepository extends JpaRepository<AssignmentItem,
// 보증금
int countByUserAndSubmitted(User user, AssignmentStatus status);

Optional<AssignmentItem> findByUserAndAssignment(User user, Assignment assignment);
// Optional 처리로 오류 발생 check

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import backend.pirocheck.Assignment.dto.request.AssignmentCreateReq;
import backend.pirocheck.Assignment.dto.request.AssignmentItemCreateReq;
import backend.pirocheck.Assignment.dto.request.AssignmentItemUpdateReq;
import backend.pirocheck.Assignment.dto.request.AssignmentUpdateReq;
import backend.pirocheck.Assignment.dto.response.AssignmentDayRes;
import backend.pirocheck.Assignment.dto.response.AssignmentDetailRes;
Expand All @@ -14,6 +15,7 @@
import backend.pirocheck.User.entity.User;
import backend.pirocheck.User.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -22,6 +24,7 @@
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j // 로그를 찍기위해 사용
@Service
@Transactional
@RequiredArgsConstructor
Expand Down Expand Up @@ -118,7 +121,9 @@ public String updateAssignment(Long assignmentId, AssignmentUpdateReq req) {
}

// 과제 채점 결과 저장
public AssignmentStatus createAssignmentItem(Long assignmentId, Long userId, AssignmentItemCreateReq req) {
public AssignmentStatus createAssignmentItem(Long userId, Long assignmentId, AssignmentItemCreateReq req) {
log.info("userId 요청 값: {}", userId);

User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("조회된 사용자가 없습니다."));

Expand All @@ -135,4 +140,22 @@ public AssignmentStatus createAssignmentItem(Long assignmentId, Long userId, Ass

return assignmentItem.getSubmitted();
}

// 과제 채점 결과 수정
public AssignmentStatus updateAssignmentItem(Long userId, Long assignmentId, AssignmentItemUpdateReq req) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("조회된 사용자가 없습니다."));

Assignment assignment = assignmentRepository.findById(assignmentId)
.orElseThrow(() -> new IllegalArgumentException("조회된 과제가 없습니다."));

AssignmentItem assignmentItem = assignmentItemRepository.findByUserAndAssignment(user, assignment)
.orElseThrow(() -> new IllegalArgumentException("해당 유저의 과제 채점 결과가 없습니다."));

assignmentItem.update(req.getStatus()); // 상태 업데이트

assignmentItemRepository.save(assignmentItem); // 상태 저장

return assignmentItem.getSubmitted();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package backend.pirocheck.Attendance.controller;

import backend.pirocheck.Attendance.dto.request.UpdateAttendanceStatusReq;
import backend.pirocheck.Attendance.dto.response.ApiResponse;
import backend.pirocheck.Attendance.dto.response.AttendanceCodeResponse;
import backend.pirocheck.Attendance.dto.response.UserAttendanceStatusRes;
import backend.pirocheck.Attendance.entity.AttendanceCode;
import backend.pirocheck.Attendance.service.AttendanceService;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -12,8 +14,11 @@
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

@RestController
Expand Down Expand Up @@ -79,7 +84,7 @@ public ApiResponse<AttendanceCodeResponse> getActiveCode() {
public ApiResponse<Void> expireAttendance(
@Parameter(description = "만료할 출석 코드", required = true)
@RequestParam String code) {
String result = attendanceService.exprireAttendanceCode(code);
String result = attendanceService.expireAttendanceCode(code);

if (result.equals("출석 코드가 성공적으로 만료되었습니다")) {
return ApiResponse.success(result, null);
Expand All @@ -104,4 +109,49 @@ public ApiResponse<Void> expireLatestAttendance() {
return ApiResponse.error(result);
}
}

// 출석 상태 변경 (관리자 전용)
@Operation(summary = "출석 상태 변경", description = "관리자가 특정 사용자의 출석 상태를 변경합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "출석 상태 변경 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "출석 기록을 찾을 수 없음")
})
@PutMapping("/status")
public ApiResponse<Void> updateAttendanceStatus(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "출석 상태 변경 요청",
required = true,
content = @Content(schema = @Schema(implementation = UpdateAttendanceStatusReq.class))
)
@RequestBody UpdateAttendanceStatusReq req) {

boolean result = attendanceService.updateAttendanceStatus(
req.getAttendanceId(),
req.isStatus()
);

if (result) {
return ApiResponse.success("출석 상태가 성공적으로 변경되었습니다", null);
} else {
return ApiResponse.error("출석 상태 변경에 실패했습니다. 출석 기록을 찾을 수 없습니다.");
}
}

// 특정 날짜와 차수에 대한 모든 학생의 출석 현황 조회
@Operation(summary = "특정 날짜와 차수의 출석 현황 조회", description = "특정 날짜와 차수에 대한 모든 학생의 출석 현황을 조회합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청")
})
@GetMapping("/list")
public ApiResponse<List<UserAttendanceStatusRes>> getAllAttendanceByDateAndOrder(
@Parameter(description = "조회할 날짜 (YYYY-MM-DD)", required = true)
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
@Parameter(description = "조회할 차수", required = true)
@RequestParam int order) {

List<UserAttendanceStatusRes> attendances = attendanceService.findAllByDateAndOrder(date, order);
return ApiResponse.success(attendances);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package backend.pirocheck.Attendance.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "출석 상태 수정 요청")
public class UpdateAttendanceStatusReq {
@Schema(description = "출석 기록 ID", example = "1")
private Long attendanceId;

@Schema(description = "변경할 출석 상태", example = "true")
private boolean status;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package backend.pirocheck.Attendance.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "사용자 출석 상태 응답")
public class UserAttendanceStatusRes {
@Schema(description = "출석 기록 ID", example = "1")
private Long attendanceId;

@Schema(description = "사용자 ID", example = "1")
private Long userId;

@Schema(description = "사용자 이름", example = "홍길동")
private String username;

@Schema(description = "출석 날짜", example = "2023-10-20")
private LocalDate date;

@Schema(description = "출석 차수", example = "1")
private int order;

@Schema(description = "출석 상태", example = "true")
private boolean status;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ public interface AttendanceRepository extends JpaRepository<Attendance, Long> {

// 출석 실패
int countByUserAndStatusFalse(User user);

// 특정 날짜와 차수에 대한 모든 출석 기록 조회
List<Attendance> findByDateAndOrder(LocalDate date, int order);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import backend.pirocheck.Attendance.dto.response.AttendanceMarkResponse;
import backend.pirocheck.Attendance.dto.response.AttendanceSlotRes;
import backend.pirocheck.Attendance.dto.response.AttendanceStatusRes;
import backend.pirocheck.Attendance.dto.response.UserAttendanceStatusRes;
import backend.pirocheck.Attendance.entity.Attendance;
import backend.pirocheck.Attendance.entity.AttendanceCode;
import backend.pirocheck.Attendance.repository.AttendanceCodeRepository;
Expand Down Expand Up @@ -104,7 +105,7 @@ public String expireLatestAttendanceCode() {

// 출석코드 만료처리 함수
@Transactional
public String exprireAttendanceCode(String code) {
public String expireAttendanceCode(String code) {
Optional<AttendanceCode> codeOpt = attendanceCodeRepository.findByCodeAndDate(code, LocalDate.now());

if (codeOpt.isEmpty()) {
Expand Down Expand Up @@ -208,4 +209,42 @@ public List<AttendanceSlotRes> findByUserIdAndDate(Long userId, LocalDate date)
.sorted(Comparator.comparingInt(AttendanceSlotRes::getOrder))
.toList();
}

// 관리자가 유저의 출석 상태를 변경하는 함수
@Transactional
public boolean updateAttendanceStatus(Long attendanceId, boolean status) {
Optional<Attendance> attendanceOpt = attendanceRepository.findById(attendanceId);

if (attendanceOpt.isEmpty()) {
return false;
}

// 출석 상태 변경
Attendance attendance = attendanceOpt.get();
attendance.setStatus(status);
attendanceRepository.save(attendance);
return true;
}

// 특정 날짜와 차수의 모든 학생 출석 현황 조회
public List<UserAttendanceStatusRes> findAllByDateAndOrder(LocalDate date, int order) {
// 해당 날짜와 차수에 대한 모든 출석 기록 조회
List<Attendance> attendances = attendanceRepository.findByDateAndOrder(date, order);

// 사용자별로 DTO 변환
return attendances.stream()
.map(attendance -> {
User user = attendance.getUser();
return UserAttendanceStatusRes.builder()
.userId(user.getId())
.username(user.getName())
.date(attendance.getDate())
.order(attendance.getOrder())
.status(attendance.isStatus())
.attendanceId(attendance.getId()) // 출석 기록 ID 추가
.build();
})
.sorted(Comparator.comparing(UserAttendanceStatusRes::getUsername))
.toList();
}
}
9 changes: 9 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Assignment from "./pages/generation/Assignment";
import Deposit from "./pages/generation/Deposit";
import Intro from "./Intro";
import Admin from "./pages/admin/Admin";
import DetailManageStudent from "./pages/admin/DetailManageStudent.jsx";
import ManageStudent from "./pages/admin/ManageStudent.jsx";
import ManageTask from "./pages/admin/ManageTask.jsx";
import AttendanceCode from "./pages/admin/AttendanceCode";
Expand Down Expand Up @@ -68,6 +69,14 @@ function App() {
</RequireAdmin>
}
/>
<Route
path="/managestudent/detail/:studentId"
element={
<RequireAdmin>
<DetailManageStudent />
</RequireAdmin>
}
/>
<Route
path="/managetask"
element={
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/api/students.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,13 @@ export const getStudentsByName = async (name) => {
});
return res.data; // [{ id: ..., name: ... }]
};

export const getStudentDetail = async (studentId) => {
try {
const res = await api.get(`/admin/managestudent/${studentId}`);
return res.data;
} catch (error) {
console.error("학생 상세 정보 불러오기 실패:", error);
throw error;
}
};
Loading