From 8631e912f5d8c376882f7ad0fccf76fae07a2469 Mon Sep 17 00:00:00 2001 From: dietken1 Date: Fri, 16 May 2025 12:19:53 +0900 Subject: [PATCH 01/66] =?UTF-8?q?=EC=B6=9C=EC=84=9D=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EB=A1=9C=EC=A7=81,=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=ED=98=95=EC=8B=9D=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?swagger=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AttendanceController.java | 97 +++++++++---------- .../dto/request/MarkAttendanceReq.java | 5 + .../dto/response/AttendanceCodeResponse.java | 9 ++ .../dto/response/AttendanceSlotRes.java | 6 +- .../dto/response/AttendanceStatusRes.java | 8 ++ .../Attendance/service/AttendanceService.java | 37 +++++-- .../src/main/resources/application.yml | 18 +++- 7 files changed, 118 insertions(+), 62 deletions(-) diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AttendanceController.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AttendanceController.java index 2401afb..103135c 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AttendanceController.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AttendanceController.java @@ -2,93 +2,88 @@ import backend.pirocheck.Attendance.dto.request.MarkAttendanceReq; import backend.pirocheck.Attendance.dto.response.ApiResponse; -import backend.pirocheck.Attendance.dto.response.AttendanceCodeResponse; +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.entity.AttendanceCode; import backend.pirocheck.Attendance.service.AttendanceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +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 @RequiredArgsConstructor @RequestMapping("/api/attendance") +@Tag(name = "출석관리", description = "학생용 출석 관련 API") public class AttendanceController { private final AttendanceService attendanceService; // 특정 유저의 출석 정보 + @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 = "사용자를 찾을 수 없음") + }) @GetMapping("/user") - public ApiResponse> getAttendanceByUserId(@RequestParam Long userId) { + public ApiResponse> getAttendanceByUserId( + @Parameter(description = "사용자 ID", required = true) + @RequestParam Long userId) { return ApiResponse.success(attendanceService.findByUserId(userId)); } // 특정 유저의 특정 일자 출석 정보 + @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 = "사용자 또는 날짜 정보를 찾을 수 없음") + }) @GetMapping("/user/date") public ApiResponse> getAttendanceByUserIdAndDate( + @Parameter(description = "사용자 ID", required = true) @RequestParam Long userId, + @Parameter(description = "조회할 날짜 (YYYY-MM-DD)", required = true) @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date ) { return ApiResponse.success(attendanceService.findByUserIdAndDate(userId, date)); } - // 출석체크 시작 - @PostMapping("/start") - public ApiResponse postAttendance() { - AttendanceCode code = attendanceService.generateCodeAndCreateAttendances(); - return ApiResponse.success(AttendanceCodeResponse.from(code)); - } - - // 현재 활성화된 출석코드 조회 - @GetMapping("/active-code") - public ApiResponse getActiveCode() { - Optional codeOpt = attendanceService.getActiveAttendanceCode(); - - if (codeOpt.isEmpty()) { - return ApiResponse.error("현재 활성화된 출석코드가 없습니다"); - } - - return ApiResponse.success(AttendanceCodeResponse.from(codeOpt.get())); - } - // 출석코드 비교 + @Operation(summary = "출석 체크", description = "출석 코드를 입력하여 출석을 체크합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "출석 성공 또는 이미 출석 완료"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 출석 코드 또는 출석 체크 진행중이 아님") + }) @PostMapping("/mark") - public ApiResponse markAttendance(@RequestBody MarkAttendanceReq req) { - String result = attendanceService.markAttendance(req.getUserId(), req.getCode()); - - if (result.equals("출석이 성공적으로 처리되었습니다")) { - return ApiResponse.success(result, null); - } else { - return ApiResponse.error(result); - } - } - - // 출석체크 종료 (코드 직접 전달) - @PutMapping("/expire") - public ApiResponse expireAttendance(@RequestParam String code) { - String result = attendanceService.exprireAttendanceCode(code); + public ApiResponse markAttendance( + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "출석 체크 요청", required = true, + content = @Content(schema = @Schema(implementation = MarkAttendanceReq.class))) + @RequestBody MarkAttendanceReq req) { + AttendanceMarkResponse response = attendanceService.markAttendance(req.getUserId(), req.getCode()); - if (result.equals("출석 코드가 성공적으로 만료되었습니다")) { - return ApiResponse.success(result, null); - } else { - return ApiResponse.error(result); - } - } - - // 출석체크 종료 (가장 최근 활성화된 코드 자동 만료) - @PutMapping("/expire-latest") - public ApiResponse expireLatestAttendance() { - String result = attendanceService.expireLatestAttendanceCode(); + // statusCode가 SUCCESS 또는 ALREADY_MARKED인 경우 성공으로 처리 + boolean isSuccess = "SUCCESS".equals(response.getStatusCode()) || + "ALREADY_MARKED".equals(response.getStatusCode()); - if (result.equals("출석 코드가 성공적으로 만료되었습니다")) { - return ApiResponse.success(result, null); + if (isSuccess) { + return ApiResponse.success(response); } else { - return ApiResponse.error(result); + // 그 외의 경우 (NO_ACTIVE_SESSION, CODE_EXPIRED, ERROR)는 오류로 처리 + return ApiResponse.builder() + .success(false) + .message(response.getMessage()) + .data(response) + .build(); } } } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/MarkAttendanceReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/MarkAttendanceReq.java index 779ead3..450728e 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/MarkAttendanceReq.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/MarkAttendanceReq.java @@ -1,9 +1,14 @@ package backend.pirocheck.Attendance.dto.request; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter +@Schema(description = "출석 체크 요청") public class MarkAttendanceReq { + @Schema(description = "사용자 ID", example = "1") private Long userId; + + @Schema(description = "출석 코드", example = "1234") private String code; } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceCodeResponse.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceCodeResponse.java index 4fe0b37..ca3dc4b 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceCodeResponse.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceCodeResponse.java @@ -1,6 +1,7 @@ package backend.pirocheck.Attendance.dto.response; import backend.pirocheck.Attendance.entity.AttendanceCode; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -12,10 +13,18 @@ @Builder @NoArgsConstructor @AllArgsConstructor +@Schema(description = "출석 코드 응답") public class AttendanceCodeResponse { + @Schema(description = "출석 코드", example = "1234") private String code; + + @Schema(description = "출석 날짜", example = "2025-06-24") private LocalDate date; + + @Schema(description = "출석 차시 (1, 2, 3)", example = "1") private int order; + + @Schema(description = "만료 여부", example = "false") private boolean isExpired; public static AttendanceCodeResponse from(AttendanceCode attendanceCode) { diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceSlotRes.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceSlotRes.java index aa1a5cc..8a804bd 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceSlotRes.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceSlotRes.java @@ -1,5 +1,6 @@ package backend.pirocheck.Attendance.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -7,8 +8,11 @@ @Getter @Setter @AllArgsConstructor +@Schema(description = "출석 차시별 상태") public class AttendanceSlotRes { + @Schema(description = "출석 차시 (1, 2, 3)", example = "1") private int order; + + @Schema(description = "출석 여부", example = "true") private boolean status; - } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceStatusRes.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceStatusRes.java index 7a0cfa4..6716f8a 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceStatusRes.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceStatusRes.java @@ -1,5 +1,6 @@ package backend.pirocheck.Attendance.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @@ -8,7 +9,14 @@ @Getter @Setter +@Schema(description = "사용자 출석 상태") public class AttendanceStatusRes { + @Schema(description = "출석 날짜", example = "2025-06-24") private LocalDate date; + + @Schema(description = "주차", example = "1") + private int week; + + @Schema(description = "출석 차시별 상태 목록") private List slots; } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java index de65f49..ae2d5a8 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java @@ -3,6 +3,7 @@ import backend.pirocheck.User.entity.Role; import backend.pirocheck.User.entity.User; import backend.pirocheck.User.repository.UserRepository; +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.entity.Attendance; @@ -43,6 +44,11 @@ public AttendanceCode generateCodeAndCreateAttendances() { // 오늘 생성된 출석코드 개수 = 현재까지 생성된 차시 수 + 1 (MAX=3) int currentOrder = attendanceCodeRepository.countByDate(today) + 1; + + // 하루 최대 3회 출석 체크만 허용 + if (currentOrder > 3) { + throw new IllegalStateException("하루에 최대 3회까지만 출석 체크를 진행할 수 있습니다."); + } // 1. 출석 코드 생성 String code = String.valueOf(ThreadLocalRandom.current().nextInt(1000, 10000)); @@ -119,25 +125,38 @@ public String exprireAttendanceCode(String code) { // 출석처리 함수 @Transactional - public String markAttendance(Long userId, String inputCode) { - // 1. 출석코드 일치 비교 - Optional validCodeOpt = attendanceCodeRepository.findByCodeAndDate(inputCode, LocalDate.now()); - + public AttendanceMarkResponse markAttendance(Long userId, String inputCode) { + // 오늘 날짜 + LocalDate today = LocalDate.now(); + + // 현재 활성화된 출석 코드가 있는지 확인 + List activeCodes = attendanceCodeRepository.findByDateAndIsExpiredFalse(today); + + // 활성화된 출석 코드가 없는 경우 + if (activeCodes.isEmpty()) { + return AttendanceMarkResponse.noActiveSession(); + } + + // 입력한 출석 코드와 일치하는 코드가 있는지 확인 + Optional validCodeOpt = attendanceCodeRepository.findByCodeAndDate(inputCode, today); + + // 입력한 출석 코드가 존재하지 않는 경우 if (validCodeOpt.isEmpty()) { - return "출석 코드가 존재하지 않습니다. 현재 출석 체크가 진행중이 아닙니다"; + return AttendanceMarkResponse.invalidCode(); } AttendanceCode code = validCodeOpt.get(); + // 입력한 출석 코드가 만료된 경우 if (code.isExpired()) { - return "출석 코드가 만료되었습니다"; + return AttendanceMarkResponse.codeExpired(); } // 2. 해당 유저의 출석 레코드 조회 Optional attendanceOpt = attendanceRepository.findByUserIdAndDateAndOrder(userId, code.getDate(), code.getOrder()); if (attendanceOpt.isEmpty()) { - return "출석 정보를 찾을 수 없습니다"; + return AttendanceMarkResponse.error("출석 정보를 찾을 수 없습니다"); } // 3. 출석 처리 @@ -145,13 +164,13 @@ public String markAttendance(Long userId, String inputCode) { // 이미 출석한 경우 if (attendance.isStatus()) { - return "이미 출석처리가 완료되었습니다"; + return AttendanceMarkResponse.alreadyMarked(); } attendance.setStatus(true); attendanceRepository.save(attendance); - return "출석이 성공적으로 처리되었습니다"; + return AttendanceMarkResponse.success(); } // 유저의 전체 출석 현황을 조회하는 함수 diff --git a/backend/pirocheck/src/main/resources/application.yml b/backend/pirocheck/src/main/resources/application.yml index b8b7096..bdef4aa 100644 --- a/backend/pirocheck/src/main/resources/application.yml +++ b/backend/pirocheck/src/main/resources/application.yml @@ -20,4 +20,20 @@ server: secure: false # HTTPS 전용 전송 (Https -> true로 바꿔야 함) same-site: Lax # CSRF 방지 timeout: 30m # 세션 타임아웃 30분 (30 minutes) - address: 0.0.0.0 \ No newline at end of file + address: 0.0.0.0 + +# Swagger 설정 +springdoc: + packages-to-scan: backend.pirocheck + default-consumes-media-type: application/json + default-produces-media-type: application/json + swagger-ui: + path: /swagger-ui.html + disable-swagger-default-url: true + display-request-duration: true + tags-sorter: alpha + operations-sorter: alpha + api-docs: + path: /api-docs + cache: + disabled: true \ No newline at end of file From f273715c384ca9198a02b747ca0a476dfe847af1 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Sun, 18 May 2025 20:55:04 +0900 Subject: [PATCH 02/66] [add]: ManageStudent.jsx --- frontend/src/components/Header.jsx | 2 +- frontend/src/pages/admin/ManageStudent.jsx | 29 ++++++++++++++++++- .../src/pages/admin/ManageStudent.module.css | 6 ++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 frontend/src/pages/admin/ManageStudent.module.css diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 5c3501d..914c888 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -10,7 +10,7 @@ const Header = () => { if (path.includes("assignment")) title = "ASSIGNMENT\nCHECK"; else if (path.includes("deposit")) title = "DEPOSIT"; else if (path.includes("attendance")) title = "ATTENDANCE\nCHECK"; - else if (path.includes("magagestudent")) title = "수강생관리"; + else if (path.includes("magagestudent")) title = "수강생 관리"; else if (path.includes("magagetask")) title = "과제 관리"; else if (path.includes("attendancecode")) title = "출석코드 생성"; diff --git a/frontend/src/pages/admin/ManageStudent.jsx b/frontend/src/pages/admin/ManageStudent.jsx index 41f63e8..eef36a7 100644 --- a/frontend/src/pages/admin/ManageStudent.jsx +++ b/frontend/src/pages/admin/ManageStudent.jsx @@ -1,4 +1,31 @@ +import { useState } from "react"; +import Header from "../../components/Header"; +import InputBlock from "../../components/InputBlock"; +import style from "./ManageStudent.module.css"; + const MagageStudent = () => { - return

수강생 관리

; + const [studentName, setStudentName] = useState([""]); + + const handleChange = (index, value) => { + const studentNames = [...studentName]; + studentNames[index] = value; + setStudentName(studentNames); + }; + + return ( +
+
+ +
+ ); }; export default MagageStudent; diff --git a/frontend/src/pages/admin/ManageStudent.module.css b/frontend/src/pages/admin/ManageStudent.module.css new file mode 100644 index 0000000..5e87c6a --- /dev/null +++ b/frontend/src/pages/admin/ManageStudent.module.css @@ -0,0 +1,6 @@ +.managestudent_wrapper { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; +} From c687d0d6cda53dead33865f6aa0e2b5413cdae95 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Sun, 18 May 2025 21:04:29 +0900 Subject: [PATCH 03/66] =?UTF-8?q?[fix]:=20ManageStudent.jsx=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.jsx | 4 ++-- frontend/src/components/Header.jsx | 2 +- frontend/src/pages/admin/Admin.jsx | 2 +- frontend/src/pages/admin/ManageStudent.jsx | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b11b9d0..0b549ed 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,7 +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 MagageStudent from "./pages/admin/ManageStudent.jsx"; +import ManageStudent from "./pages/admin/ManageStudent.jsx"; import ManageTask from "./pages/admin/ManageTask.jsx"; import AttendanceCode from "./pages/admin/AttendanceCode"; import Attendance from "./pages/generation/Attendance"; @@ -22,7 +22,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 914c888..fe0403d 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -10,7 +10,7 @@ const Header = () => { if (path.includes("assignment")) title = "ASSIGNMENT\nCHECK"; else if (path.includes("deposit")) title = "DEPOSIT"; else if (path.includes("attendance")) title = "ATTENDANCE\nCHECK"; - else if (path.includes("magagestudent")) title = "수강생 관리"; + else if (path.includes("managestudent")) title = "수강생 관리"; else if (path.includes("magagetask")) title = "과제 관리"; else if (path.includes("attendancecode")) title = "출석코드 생성"; diff --git a/frontend/src/pages/admin/Admin.jsx b/frontend/src/pages/admin/Admin.jsx index fa42daf..6b9e4e9 100644 --- a/frontend/src/pages/admin/Admin.jsx +++ b/frontend/src/pages/admin/Admin.jsx @@ -9,7 +9,7 @@ const Admin = () => {

PIROCHECK

diff --git a/frontend/src/pages/admin/ManageStudent.jsx b/frontend/src/pages/admin/ManageStudent.jsx index eef36a7..e54018e 100644 --- a/frontend/src/pages/admin/ManageStudent.jsx +++ b/frontend/src/pages/admin/ManageStudent.jsx @@ -3,7 +3,7 @@ import Header from "../../components/Header"; import InputBlock from "../../components/InputBlock"; import style from "./ManageStudent.module.css"; -const MagageStudent = () => { +const ManageStudent = () => { const [studentName, setStudentName] = useState([""]); const handleChange = (index, value) => { @@ -28,4 +28,4 @@ const MagageStudent = () => { ); }; -export default MagageStudent; +export default ManageStudent; From 45ec23b402f383b16e97eba14d4d85ad03003457 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Sun, 18 May 2025 21:30:15 +0900 Subject: [PATCH 04/66] =?UTF-8?q?[add]:=20local=20data=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EA=B0=95=EC=83=9D=20=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5+=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EA=B8=B0=EB=8A=A5=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/admin/ManageStudent.jsx | 81 ++++++++++++++++--- .../src/pages/admin/ManageStudent.module.css | 41 ++++++++++ 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/admin/ManageStudent.jsx b/frontend/src/pages/admin/ManageStudent.jsx index e54018e..e8ddc62 100644 --- a/frontend/src/pages/admin/ManageStudent.jsx +++ b/frontend/src/pages/admin/ManageStudent.jsx @@ -5,26 +5,85 @@ import style from "./ManageStudent.module.css"; const ManageStudent = () => { const [studentName, setStudentName] = useState([""]); - + const [page, setPage] = useState(1); + const studentsPerPage = 6; + const studentList = [ + "김피로그", + "박피로그", + "이피로그", + "최피로그", + "김피로그", + "박피로그", + "이피로그", + "최피로그", + "김피로그", + "박피로그", + "이피로그", + "최피로그", + "김피로그", + "박피로그", + "이피로그", + "최피로그", + "경민", + "경미니", + ]; + const filteredStudents = studentList.filter((name) => + name.includes(studentName[0]) + ); + const totalPages = Math.ceil(filteredStudents.length / studentsPerPage); + const paginatedStudents = filteredStudents.slice( + (page - 1) * studentsPerPage, + page * studentsPerPage + ); const handleChange = (index, value) => { const studentNames = [...studentName]; studentNames[index] = value; setStudentName(studentNames); + setPage(1); // 검색 시 페이지 초기화 }; return (
- +
+ +
+ {paginatedStudents.map((name, index) => ( + + ))} +
+ + {filteredStudents.length > studentsPerPage && ( +
+ + + {page} / {totalPages} + + +
+ )} +
); }; diff --git a/frontend/src/pages/admin/ManageStudent.module.css b/frontend/src/pages/admin/ManageStudent.module.css index 5e87c6a..014c39f 100644 --- a/frontend/src/pages/admin/ManageStudent.module.css +++ b/frontend/src/pages/admin/ManageStudent.module.css @@ -4,3 +4,44 @@ flex-direction: column; align-items: center; } +.student_list { + margin-top: 60px; + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; +} +.under_header { + display: flex; + flex-direction: column; + align-items: center; +} +.student_button { + background-color: #333; + color: white; + padding: 15px; + border-radius: 8px; + border: 1px solid #39ff14; + text-align: left; + font-size: 16px; + width: 100%; +} +.pagination { + margin-top: 60px; + display: flex; + justify-content: center; + align-items: center; + gap: 12px; +} +.pagination button { + background-color: #111; + color: #39ff14; + padding: 8px 12px; + border: 1px solid #39ff14; + border-radius: 6px; + cursor: pointer; +} +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} From 43a69606dc2408aed5189e3943645ee06165d2f9 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Sun, 18 May 2025 21:49:26 +0900 Subject: [PATCH 05/66] =?UTF-8?q?[add]:=20=EC=84=9C=EB=B2=84=EB=A1=9C?= =?UTF-8?q?=EB=B6=80=ED=84=B0=20=EC=A0=84=EC=B2=B4=20=EC=88=98=EA=B0=95?= =?UTF-8?q?=EC=83=9D=20=EC=A0=95=EB=B3=B4=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20api=20=ED=95=A8=EC=88=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/students.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 frontend/src/api/students.js diff --git a/frontend/src/api/students.js b/frontend/src/api/students.js new file mode 100644 index 0000000..eac3665 --- /dev/null +++ b/frontend/src/api/students.js @@ -0,0 +1,8 @@ +import api from "./api"; + +export const getStudentsByName = async (name) => { + const res = await api.get("/admin/managestudent", { + params: { name }, + }); + return res.data; // [{ id: ..., name: ... }] +}; From 61e9205230999dd99940dd2fc4ba9339c00f33b8 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Sun, 18 May 2025 21:50:47 +0900 Subject: [PATCH 06/66] =?UTF-8?q?[feat]:=20api.js=20baseURL=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=ED=95=84?= =?UTF-8?q?=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js index 2cd0305..330d870 100644 --- a/frontend/src/api/api.js +++ b/frontend/src/api/api.js @@ -1,7 +1,7 @@ import axios from "axios"; const api = axios.create({ - baseURL: "http://api.:8080/api", + baseURL: "http://api.:8080/api", // 수정 필요한지 재검 필요함 "http://api.pirocheck.org:8080/api" withCredentials: true, }); From 4e06f631bf82442bdd6ab1df3971d19ad83066f3 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Sun, 18 May 2025 22:01:14 +0900 Subject: [PATCH 07/66] =?UTF-8?q?[fix]:=20api.js=20baseURL=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/api.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js index 330d870..7dc75ea 100644 --- a/frontend/src/api/api.js +++ b/frontend/src/api/api.js @@ -1,7 +1,10 @@ import axios from "axios"; const api = axios.create({ - baseURL: "http://api.:8080/api", // 수정 필요한지 재검 필요함 "http://api.pirocheck.org:8080/api" + baseURL: "http://api.pirocheck.org:8080/api", + // 수정 필요한지 재검 필요함 + // "http://api.pirocheck.org:8080/api" + // 기존: "http://api.:8080/api" withCredentials: true, }); From c52bbbfdcd08cf9d9602fabc31aa13d675a0c9d5 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Sun, 18 May 2025 22:01:38 +0900 Subject: [PATCH 08/66] =?UTF-8?q?[fix]:=20=EC=84=9C=EB=B2=84=EB=A1=9C?= =?UTF-8?q?=EB=B6=80=ED=84=B0=20=EC=88=98=EA=B0=95=EC=83=9D=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8A=94=20api=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/admin/ManageStudent.jsx | 68 ++++++++++------------ 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/frontend/src/pages/admin/ManageStudent.jsx b/frontend/src/pages/admin/ManageStudent.jsx index e8ddc62..252175f 100644 --- a/frontend/src/pages/admin/ManageStudent.jsx +++ b/frontend/src/pages/admin/ManageStudent.jsx @@ -1,4 +1,5 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; +import { getStudentsByName } from "../../api/students"; import Header from "../../components/Header"; import InputBlock from "../../components/InputBlock"; import style from "./ManageStudent.module.css"; @@ -6,42 +7,37 @@ import style from "./ManageStudent.module.css"; const ManageStudent = () => { const [studentName, setStudentName] = useState([""]); const [page, setPage] = useState(1); + const [students, setStudents] = useState([]); // 서버 데이터 저장 + const studentsPerPage = 6; - const studentList = [ - "김피로그", - "박피로그", - "이피로그", - "최피로그", - "김피로그", - "박피로그", - "이피로그", - "최피로그", - "김피로그", - "박피로그", - "이피로그", - "최피로그", - "김피로그", - "박피로그", - "이피로그", - "최피로그", - "경민", - "경미니", - ]; - const filteredStudents = studentList.filter((name) => - name.includes(studentName[0]) - ); - const totalPages = Math.ceil(filteredStudents.length / studentsPerPage); - const paginatedStudents = filteredStudents.slice( - (page - 1) * studentsPerPage, - page * studentsPerPage - ); + + useEffect(() => { + const fetchStudents = async () => { + try { + const name = studentName[0] || ""; + const data = await getStudentsByName(name); + setStudents(data); + } catch (err) { + console.error("수강생 불러오기 실패:", err); + } + }; + + fetchStudents(); + }, [studentName]); + const handleChange = (index, value) => { - const studentNames = [...studentName]; - studentNames[index] = value; - setStudentName(studentNames); + const newNames = [...studentName]; + newNames[index] = value; + setStudentName(newNames); setPage(1); // 검색 시 페이지 초기화 }; + const totalPages = Math.ceil(students.length / studentsPerPage); + const paginatedStudents = students.slice( + (page - 1) * studentsPerPage, + page * studentsPerPage + ); + return (
@@ -57,14 +53,14 @@ const ManageStudent = () => { onChange={handleChange} />
- {paginatedStudents.map((name, index) => ( - ))}
- {filteredStudents.length > studentsPerPage && ( + {students.length > studentsPerPage && (
diff --git a/frontend/src/pages/admin/ManageTask.jsx b/frontend/src/pages/admin/ManageTask.jsx index 7140e6e..713095d 100644 --- a/frontend/src/pages/admin/ManageTask.jsx +++ b/frontend/src/pages/admin/ManageTask.jsx @@ -1,4 +1,11 @@ -const MagageTask = () => { - return

과제 관리

; +import Header from "../../components/Header"; +import style from "./ManageTask.module.css"; + +const ManageTask = () => { + return ( +
+
+
+ ); }; -export default MagageTask; +export default ManageTask; diff --git a/frontend/src/pages/admin/ManageTask.module.css b/frontend/src/pages/admin/ManageTask.module.css new file mode 100644 index 0000000..9099a04 --- /dev/null +++ b/frontend/src/pages/admin/ManageTask.module.css @@ -0,0 +1,6 @@ +.managetask_wrapper { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; +} From cf79278e06fcd3362c991b31e9df27b934b81ae7 Mon Sep 17 00:00:00 2001 From: l-wanderer01 Date: Mon, 19 May 2025 00:27:27 +0900 Subject: [PATCH 14/66] =?UTF-8?q?[feat]=20swagger=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=ED=99=94=20=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AssignmentController.java | 79 +++++++++++++++++-- .../dto/request/AssignmentCreateReq.java | 13 +++ .../dto/request/AssignmentUpdateReq.java | 14 ++++ .../Assignment/entity/Assignment.java | 2 + 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java index 879b048..c74d85f 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java @@ -4,6 +4,12 @@ import backend.pirocheck.Assignment.dto.request.AssignmentUpdateReq; import backend.pirocheck.Assignment.dto.response.AssignmentWeekRes; import backend.pirocheck.Assignment.service.AssignmentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -12,49 +18,110 @@ @RestController @RequestMapping("/api") @RequiredArgsConstructor +@Tag(name = "과제관리", description = "과제 관련 API") public class AssignmentController { private final AssignmentService assignmentService; // 과제 결과 확인 API // 과제 주차별, 요일별 그룹화 JSON + @Operation(summary = "학생별 과제 결과 확인", description = "관리자가 채점한 과제의 결과를 학생들이 확인합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "사용자별 과제 조회에 성공하였습니다."), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.") + } + ) @GetMapping("/assignment/{userId}") - public List getGroupedAssignments(@PathVariable Long userId) { + public List getGroupedAssignments( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId + ) { return assignmentService.search(userId); } // 과제 생성 API + @Operation(summary = "과제 생성 API", description = "관리자가 과제를 생성합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "과제 생성에 성공하였습니다."), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.") + } + ) @PostMapping("/admin/assignment/signup") - public String signupAssignment(@RequestBody AssignmentCreateReq assignmentCreateReq) { + public String signupAssignment( + @Valid + @RequestBody AssignmentCreateReq assignmentCreateReq + ) { return assignmentService.createAssignment(assignmentCreateReq); } // 과제 삭제 API + @Operation(summary = "과제 삭제 API", description = "관리자가 과제를 삭제합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "과제 삭제에 성공하였습니다."), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.") + } + ) @DeleteMapping("/admin/assignment/{assignmentId}") public String deleteAssignment( + @Parameter(description = "과제 ID", example = "1") @PathVariable Long assignmentId ) { return assignmentService.deleteAssignment(assignmentId); } // 과제 수정 API + @Operation(summary = "과제 수정 API", description = "관리자가 과제의 잘못된 부분을 수정합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "과제 수정에 성공하였습니다."), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.") + } + ) @PutMapping("/admin/assignment/{assignmentId}") public String updateAssignment( + @Parameter(description = "과제 ID", example = "1") @PathVariable("assignmentId") Long assignmentId, @RequestBody AssignmentUpdateReq assignmentUpdateReq ) { return assignmentService.updateAssignment(assignmentId, assignmentUpdateReq); } - // 사용자별 과제 제출 여부 생성 API + // 사용자별 과제 제출 결과 생성 API + @Operation(summary = "관리자 과제 채점 API", description = "관리자가 사용자들의 과제를 채점한 결과 저장합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "사용자의 과제 채점 결과 저장에 성공하였습니다."), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.") + } + ) @PostMapping("/admin/users/{userId}/assignments/{assignmentId}/submission") - public String submissionAssignment(@PathVariable Long userId, @PathVariable Long assignmentId) { + public String submissionAssignment( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "과제 ID", example = "1") + @PathVariable Long assignmentId + ) { return null; } // 사용자별 과제 제출 여부 수정 API - @PutMapping("/api/users/{userId}/assignments/{assignmentId}/submission") - public String updateSubmission(@PathVariable Long userId, @PathVariable Long assignmentId) { + @Operation(summary = "관리자 과제 채점 내용 수정 API", description = "관리자가 사용자의 과제 결과를 수정하여 저장합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "사용자 과제 채점 결과 수정에 성공하였습니다."), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.") + } + ) + @PutMapping("/admin/users/{userId}/assignments/{assignmentId}/submission") + public String updateSubmission( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "과제 ID", example = "1") + @PathVariable Long assignmentId + ) { return null; } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java index 123e16e..b12e833 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java @@ -1,5 +1,8 @@ package backend.pirocheck.Assignment.dto.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -7,9 +10,19 @@ @AllArgsConstructor public class AssignmentCreateReq { + @Schema(description = "과제명", example = "제로초 인강") + @NotNull(message = "과제명은 필수입니다.") private String assignmentName; + + @Schema(description = "주차", example = "1") + @NotBlank(message = "주차를 입력해주세요.") private Long week; + + @Schema(description = "요일", example = "화") + @NotBlank(message = "요일을 입력해주세요.") private String day; + + @Schema(description = "해당 일자 과제 numbering", example = "1") private Long orderNumber; } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java index 5941f88..b4c1c29 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java @@ -1,5 +1,8 @@ package backend.pirocheck.Assignment.dto.request; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -7,8 +10,19 @@ @AllArgsConstructor public class AssignmentUpdateReq { + @Schema(description = "과제명", example = "제로초 인강") + @NotNull(message = "과제명은 필수입니다.") private String assignmentName; + + @Schema(description = "주차", example = "1") + @NotBlank(message = "주차를 입력해주세요.") private Long week; + + @Schema(description = "요일", example = "화") + @NotBlank(message = "요일을 입력해주세요.") private String day; + + @Schema(description = "해당 일자 과제 numbering", example = "1") private Long orderNumber; + } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java index c51b0d3..16d64f7 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java @@ -1,5 +1,6 @@ package backend.pirocheck.Assignment.entity; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.persistence.*; import lombok.*; @@ -11,6 +12,7 @@ @Builder(access = AccessLevel.PRIVATE) @NoArgsConstructor @AllArgsConstructor +@Tag(name = "과제 관리", description = "과제 관련 API") public class Assignment { @Id From b86457c66e1ab235250a1470ac3863451ee179b6 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Mon, 19 May 2025 00:51:51 +0900 Subject: [PATCH 15/66] =?UTF-8?q?[add]:=20=EA=B3=BC=EC=A0=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/public/assets/img/edit.png | Bin 0 -> 10159 bytes frontend/src/components/TaskModal.jsx | 62 ++++++++++++ .../src/pages/admin/ManageStudent.module.css | 5 +- frontend/src/pages/admin/ManageTask.jsx | 41 ++++++++ .../src/pages/admin/ManageTask.module.css | 91 ++++++++++++++++++ 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 frontend/public/assets/img/edit.png create mode 100644 frontend/src/components/TaskModal.jsx diff --git a/frontend/public/assets/img/edit.png b/frontend/public/assets/img/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..639f11ceea57143af051918dea86cee2d8c9fac5 GIT binary patch literal 10159 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 literal 0 HcmV?d00001 diff --git a/frontend/src/components/TaskModal.jsx b/frontend/src/components/TaskModal.jsx new file mode 100644 index 0000000..b3a3b6b --- /dev/null +++ b/frontend/src/components/TaskModal.jsx @@ -0,0 +1,62 @@ +import { useState } from "react"; +import styles from "../pages/admin/ManageTask.module.css"; + +const TaskModal = ({ weekInfo, onClose }) => { + const [topic, setTopic] = useState(""); + const [day, setDay] = useState(""); + const [taskList, setTaskList] = useState([""]); + + const handleTaskChange = (index, value) => { + const newTasks = [...taskList]; + newTasks[index] = value; + setTaskList(newTasks); + }; + + const handleAddTask = () => { + setTaskList([...taskList, ""]); + }; + + return ( +
+
+
+

{weekInfo.week}

+ +
+
+ + setTopic(e.target.value)} + /> + + setDay(e.target.value)} + /> + + {taskList.map((task, i) => ( + handleTaskChange(i, e.target.value)} + /> + ))} + +
+
+ +
+
+
+ ); +}; + +export default TaskModal; diff --git a/frontend/src/pages/admin/ManageStudent.module.css b/frontend/src/pages/admin/ManageStudent.module.css index 014c39f..931680a 100644 --- a/frontend/src/pages/admin/ManageStudent.module.css +++ b/frontend/src/pages/admin/ManageStudent.module.css @@ -21,11 +21,14 @@ color: white; padding: 15px; border-radius: 8px; - border: 1px solid #39ff14; + border: 1px solid var(--fill-gray); text-align: left; font-size: 16px; width: 100%; } +.student_button:hover { + border: 1px solid #39ff14; +} .pagination { margin-top: 60px; display: flex; diff --git a/frontend/src/pages/admin/ManageTask.jsx b/frontend/src/pages/admin/ManageTask.jsx index 713095d..aa9424d 100644 --- a/frontend/src/pages/admin/ManageTask.jsx +++ b/frontend/src/pages/admin/ManageTask.jsx @@ -1,10 +1,51 @@ +import { useState } from "react"; import Header from "../../components/Header"; import style from "./ManageTask.module.css"; +import TaskModal from "../../components/TaskModal"; + +const weekData = [ + { week: "1주차", title: "", tasks: [] }, + { week: "2주차", title: "JS개론/웹개론", tasks: [] }, + { week: "3주차", title: "JS개론/웹개론", tasks: [] }, + { week: "4주차", title: "Comming soon~", tasks: [] }, + { week: "5주차", title: "Comming soon~", tasks: [] }, +]; const ManageTask = () => { + const [selectedWeekIndex, setSelectedWeekIndex] = useState(null); + const [showModal, setShowModal] = useState(false); + + const handleEditClick = (index) => { + setSelectedWeekIndex(index); + setShowModal(true); + }; + + const closeModal = () => setShowModal(false); + return (
+
+ {weekData.map((week, index) => ( +
+ + edit handleEditClick(index)} + /> +
+ ))} +
+ {showModal && ( + + )}
); }; diff --git a/frontend/src/pages/admin/ManageTask.module.css b/frontend/src/pages/admin/ManageTask.module.css index 9099a04..b93d5eb 100644 --- a/frontend/src/pages/admin/ManageTask.module.css +++ b/frontend/src/pages/admin/ManageTask.module.css @@ -4,3 +4,94 @@ flex-direction: column; align-items: center; } +.week_container { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} +.week_block { + display: flex; + align-items: center; + margin: 10px 0; + width: 100%; + justify-content: center; +} +.week_button { + background-color: #444; + color: white; + padding: 14px 16px; + border: none; + border-radius: 8px; + min-width: 250px; + margin-right: 8px; + font-size: 16px; +} +.edit_icon { + width: 20px; + height: 20px; + cursor: pointer; +} +.modal_overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: center; + align-items: center; +} +.modal { + background-color: #333; + border: 1px solid #4fff24; + border-radius: 12px; + padding: 20px; + width: 300px; + color: white; + display: flex; + flex-direction: column; + gap: 10px; +} +.modal_header { + display: flex; + justify-content: space-between; + align-items: center; +} +.close_button { + background: none; + border: none; + color: white; + font-size: 20px; + cursor: pointer; +} +.modal_body label { + margin-block: 10px; + display: block; +} +.modal_body input { + width: 100%; + padding: 6px; + margin-bottom: 8px; + background-color: #555; + border: none; + border-radius: 4px; + color: white; +} +.add_button { + background: none; + border: none; + font-size: 24px; + color: white; + cursor: pointer; +} +.save_button { + margin-top: 10px; + padding: 8px 16px; + background-color: #666; + border: none; + border-radius: 6px; + color: white; + cursor: not-allowed; +} From 0ad52c3bb565c3fcdd2edb4c14e983573418910c Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Mon, 19 May 2025 01:08:47 +0900 Subject: [PATCH 16/66] =?UTF-8?q?[add]:=20ManageTask=20+=20Modal=20form=20?= =?UTF-8?q?UI=EA=B9=8C=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/admin/ManageTask.jsx | 8 ++++---- frontend/src/pages/admin/ManageTask.module.css | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/admin/ManageTask.jsx b/frontend/src/pages/admin/ManageTask.jsx index aa9424d..82075dc 100644 --- a/frontend/src/pages/admin/ManageTask.jsx +++ b/frontend/src/pages/admin/ManageTask.jsx @@ -4,9 +4,9 @@ import style from "./ManageTask.module.css"; import TaskModal from "../../components/TaskModal"; const weekData = [ - { week: "1주차", title: "", tasks: [] }, - { week: "2주차", title: "JS개론/웹개론", tasks: [] }, - { week: "3주차", title: "JS개론/웹개론", tasks: [] }, + { week: "1주차", title: "Comming soon~", tasks: [] }, + { week: "2주차", title: "Comming soon~", tasks: [] }, + { week: "3주차", title: "Comming soon~", tasks: [] }, { week: "4주차", title: "Comming soon~", tasks: [] }, { week: "5주차", title: "Comming soon~", tasks: [] }, ]; @@ -29,7 +29,7 @@ const ManageTask = () => { {weekData.map((week, index) => (
Date: Mon, 19 May 2025 01:25:30 +0900 Subject: [PATCH 17/66] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=82=AC=EC=9A=A9=EC=9E=90=EC=9D=98=20=EA=B3=BC?= =?UTF-8?q?=EC=A0=9C=20=EC=B1=84=EC=A0=90=20=EA=B2=B0=EA=B3=BC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=ED=95=98=EB=8A=94=20=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AssignmentController.java | 9 ++++-- .../dto/request/AssignmentItemCreateReq.java | 22 +++++++++++++++ .../Assignment/entity/AssignmentItem.java | 22 +++++++++------ .../Assignment/service/AssignmentService.java | 28 ++++++++++++++++++- 4 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentItemCreateReq.java diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java index c74d85f..2759f31 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java @@ -1,8 +1,10 @@ package backend.pirocheck.Assignment.controller; import backend.pirocheck.Assignment.dto.request.AssignmentCreateReq; +import backend.pirocheck.Assignment.dto.request.AssignmentItemCreateReq; import backend.pirocheck.Assignment.dto.request.AssignmentUpdateReq; import backend.pirocheck.Assignment.dto.response.AssignmentWeekRes; +import backend.pirocheck.Assignment.entity.AssignmentStatus; import backend.pirocheck.Assignment.service.AssignmentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -98,13 +100,14 @@ public String updateAssignment( } ) @PostMapping("/admin/users/{userId}/assignments/{assignmentId}/submission") - public String submissionAssignment( + public AssignmentStatus submissionAssignment( @Parameter(description = "사용자 ID", example = "1") @PathVariable Long userId, @Parameter(description = "과제 ID", example = "1") - @PathVariable Long assignmentId + @PathVariable Long assignmentId, + @RequestBody AssignmentItemCreateReq req ) { - return null; + return assignmentService.createAssignmentItem(userId, assignmentId, req); } // 사용자별 과제 제출 여부 수정 API diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentItemCreateReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentItemCreateReq.java new file mode 100644 index 0000000..62fc718 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentItemCreateReq.java @@ -0,0 +1,22 @@ +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.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class AssignmentItemCreateReq { + + private Long assignmentId; + + private Long userId; + + @Pattern(regexp = "SUCCESS/INSUFFICIENT/FAILURE", message = "status는 SUCCESS, INSUFFICIENT 혹은 FAILURE 여야 합니다.") + @Schema(description = "과제 결과", example = "SUCCESS") + private AssignmentStatus status; +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/AssignmentItem.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/AssignmentItem.java index bb88fae..74719c7 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/AssignmentItem.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/AssignmentItem.java @@ -2,14 +2,12 @@ import backend.pirocheck.User.entity.User; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; @Entity @Getter @Setter +@Builder(access = AccessLevel.PRIVATE) @AllArgsConstructor @NoArgsConstructor public class AssignmentItem { @@ -17,16 +15,24 @@ public class AssignmentItem { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 유저별 과제 정보를 저장하는 ID - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "assignment_id") - private Assignment assignment; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "assignment_id") + private Assignment assignment; + // 과제 결과 @Enumerated(EnumType.STRING) @Column(length = 100) private AssignmentStatus submitted; // 수강생의 과제 제출여부 + + public static AssignmentItem create(User user, Assignment assignment, AssignmentStatus submitted) { + return AssignmentItem.builder() + .assignment(assignment) + .user(user) + .submitted(submitted) + .build(); + } } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java index d49cb12..b8448c9 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java @@ -1,14 +1,18 @@ package backend.pirocheck.Assignment.service; import backend.pirocheck.Assignment.dto.request.AssignmentCreateReq; +import backend.pirocheck.Assignment.dto.request.AssignmentItemCreateReq; import backend.pirocheck.Assignment.dto.request.AssignmentUpdateReq; import backend.pirocheck.Assignment.dto.response.AssignmentDayRes; import backend.pirocheck.Assignment.dto.response.AssignmentDetailRes; import backend.pirocheck.Assignment.dto.response.AssignmentWeekRes; import backend.pirocheck.Assignment.entity.Assignment; import backend.pirocheck.Assignment.entity.AssignmentItem; +import backend.pirocheck.Assignment.entity.AssignmentStatus; import backend.pirocheck.Assignment.repository.AssignmentItemRepository; import backend.pirocheck.Assignment.repository.AssignmentRepository; +import backend.pirocheck.User.entity.User; +import backend.pirocheck.User.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,6 +29,7 @@ public class AssignmentService { private final AssignmentItemRepository assignmentItemRepository; private final AssignmentRepository assignmentRepository; + private final UserRepository userRepository; public List search(Long userId) { @@ -78,11 +83,13 @@ public String createAssignment(AssignmentCreateReq assignmentCreateReq) { return assignment.getAssignmentName(); } + // 과제 삭제 public String deleteAssignment(Long assignmentId) { assignmentRepository.deleteById(assignmentId); - return "Assignment deleted successfully"; + return "과제가 성공적으로 삭제되었습니다."; } + // 과제 수정 public String updateAssignment(Long assignmentId, AssignmentUpdateReq req) { Assignment assignment = assignmentRepository.findById(assignmentId) .orElseThrow(() -> new IllegalArgumentException("조회된 과제가 없습니다.")); @@ -92,4 +99,23 @@ public String updateAssignment(Long assignmentId, AssignmentUpdateReq req) { return assignment.getAssignmentName(); } + + // 과제 채점 결과 저장 + public AssignmentStatus createAssignmentItem(Long assignmentId, Long userId, AssignmentItemCreateReq req) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("조회된 사용자가 없습니다.")); + + Assignment assignment = assignmentRepository.findById(assignmentId) + .orElseThrow(() -> new IllegalArgumentException("조회된 과제가 없습니다.")); + + AssignmentItem assignmentItem = AssignmentItem.create( + user, + assignment, + req.getStatus() + ); + + assignmentItemRepository.save(assignmentItem); + + return assignmentItem.getSubmitted(); + } } From 0284dc38d275e766d3222c1ac60c26641000255a Mon Sep 17 00:00:00 2001 From: l-wanderer01 Date: Mon, 19 May 2025 01:37:10 +0900 Subject: [PATCH 18/66] =?UTF-8?q?[fix]=20=EA=B3=BC=EC=A0=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EA=B3=BC?= =?UTF-8?q?=EC=A0=9C=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A0=84=EC=B2=B4=20=EC=A3=BC=EC=A0=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=98=EB=AF=B8=ED=95=98=EB=8A=94=20subject=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assignment/dto/request/AssignmentCreateReq.java | 4 ++++ .../Assignment/dto/request/AssignmentUpdateReq.java | 4 ++++ .../Assignment/dto/response/AssignmentWeekRes.java | 2 +- .../backend/pirocheck/Assignment/entity/Assignment.java | 9 +++++++-- .../pirocheck/Assignment/service/AssignmentService.java | 6 ++++-- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java index b12e833..d4faf7a 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java @@ -10,6 +10,10 @@ @AllArgsConstructor public class AssignmentCreateReq { + @Schema(description = "과제 주제", example = "Git/HTML/CSS") + @NotNull(message = "과제 주제는 필수입니다.") + private String subject; + @Schema(description = "과제명", example = "제로초 인강") @NotNull(message = "과제명은 필수입니다.") private String assignmentName; diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java index b4c1c29..06d1bb5 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java @@ -10,6 +10,10 @@ @AllArgsConstructor public class AssignmentUpdateReq { + @Schema(description = "과제 주제", example = "Git/HTML/CSS") + @NotNull(message = "과제 주제는 필수입니다.") + private String subject; + @Schema(description = "과제명", example = "제로초 인강") @NotNull(message = "과제명은 필수입니다.") private String assignmentName; diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/response/AssignmentWeekRes.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/response/AssignmentWeekRes.java index 687505c..b0d7721 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/response/AssignmentWeekRes.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/response/AssignmentWeekRes.java @@ -9,6 +9,6 @@ @AllArgsConstructor public class AssignmentWeekRes { private Long week; -// private String title; // 각 주차 주제 (e.g, Git / HTML / CSS) + private String subject; // 각 주차 주제 (e.g, Git / HTML / CSS) private List days; } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java index 16d64f7..410c522 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java @@ -19,6 +19,9 @@ public class Assignment { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + // 전체 주제 + private String subject; + // 과제명 private String assignmentName; @@ -43,8 +46,9 @@ public void addAssignmentItem(AssignmentItem assignmentItem) { } // 관리자가 생성 - public static Assignment create(String assignmentName, Long week, String day, Long orderNumber) { + public static Assignment create(String subject, String assignmentName, Long week, String day, Long orderNumber) { return Assignment.builder() + .subject(subject) .assignmentName(assignmentName) .week(week) .day(day) @@ -53,7 +57,8 @@ public static Assignment create(String assignmentName, Long week, String day, Lo } // 과제 내용 업데이트 - public void update(String assignmentName, Long week, String day, Long orderNumber) { + public void update(String subject, String assignmentName, Long week, String day, Long orderNumber) { + this.subject = subject; this.assignmentName = assignmentName; this.week = week; this.day = day; diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java index b8448c9..38dc73c 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java @@ -42,6 +42,7 @@ public List search(Long userId) { for (Map.Entry> entry : weekGroup.entrySet()) { Long week = entry.getKey(); + String subject = String.valueOf(entry.getKey()); List assignmentList = entry.getValue(); // day를 기준으로 그룹핑 @@ -64,7 +65,7 @@ public List search(Long userId) { assignmentDayResList.add(new AssignmentDayRes(day, assignmentDetailResList)); } - assignmentResponses.add(new AssignmentWeekRes(week, assignmentDayResList)); + assignmentResponses.add(new AssignmentWeekRes(week, subject, assignmentDayResList)); } return assignmentResponses; @@ -73,6 +74,7 @@ public List search(Long userId) { public String createAssignment(AssignmentCreateReq assignmentCreateReq) { Assignment assignment = Assignment.create( + assignmentCreateReq.getSubject(), assignmentCreateReq.getAssignmentName(), assignmentCreateReq.getWeek(), assignmentCreateReq.getDay(), @@ -94,7 +96,7 @@ public String updateAssignment(Long assignmentId, AssignmentUpdateReq req) { Assignment assignment = assignmentRepository.findById(assignmentId) .orElseThrow(() -> new IllegalArgumentException("조회된 과제가 없습니다.")); - assignment.update(req.getAssignmentName(), req.getWeek(), req.getDay(), req.getOrderNumber()); + assignment.update(req.getSubject(), req.getAssignmentName(), req.getWeek(), req.getDay(), req.getOrderNumber()); assignmentRepository.save(assignment); return assignment.getAssignmentName(); From 4f28c1c4cee18eebb254272b409c19a499d74878 Mon Sep 17 00:00:00 2001 From: l-wanderer01 Date: Mon, 19 May 2025 01:51:32 +0900 Subject: [PATCH 19/66] =?UTF-8?q?[fix]=20subject=EA=B0=80=20week=20?= =?UTF-8?q?=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EC=B6=9C=EB=A0=A5=EB=90=98?= =?UTF-8?q?=EB=8D=98=20=EA=B1=B8=20assignment=EC=9D=98=20subject=20?= =?UTF-8?q?=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EC=98=AC=EB=B0=94=EB=A5=B4?= =?UTF-8?q?=EA=B2=8C=20=EC=B6=9C=EB=A0=A5=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assignment/dto/request/AssignmentCreateReq.java | 3 ++- .../Assignment/dto/request/AssignmentUpdateReq.java | 3 ++- .../pirocheck/Assignment/service/AssignmentService.java | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java index d4faf7a..65cbe2f 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentCreateReq.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; import lombok.AllArgsConstructor; import lombok.Getter; @@ -19,7 +20,7 @@ public class AssignmentCreateReq { private String assignmentName; @Schema(description = "주차", example = "1") - @NotBlank(message = "주차를 입력해주세요.") + @Positive private Long week; @Schema(description = "요일", example = "화") diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java index 06d1bb5..5caa73a 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentUpdateReq.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; import lombok.AllArgsConstructor; import lombok.Getter; @@ -19,7 +20,7 @@ public class AssignmentUpdateReq { private String assignmentName; @Schema(description = "주차", example = "1") - @NotBlank(message = "주차를 입력해주세요.") + @Positive private Long week; @Schema(description = "요일", example = "화") diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java index 38dc73c..c4c25cd 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java @@ -41,9 +41,10 @@ public List search(Long userId) { List assignmentResponses = new ArrayList<>(); for (Map.Entry> entry : weekGroup.entrySet()) { - Long week = entry.getKey(); - String subject = String.valueOf(entry.getKey()); - List assignmentList = entry.getValue(); + Long week = entry.getKey(); // 주차 정보 + List assignmentList = entry.getValue(); // 주차에 해당하는 days의 list + + String subject = assignmentList.get(0).getAssignment().getSubject(); // day를 기준으로 그룹핑 Map> dayGroup = assignmentList.stream() From ebd76370336173e653f3ed5e9b2a61b47c0d0cb9 Mon Sep 17 00:00:00 2001 From: l-wanderer01 Date: Mon, 19 May 2025 02:31:23 +0900 Subject: [PATCH 20/66] =?UTF-8?q?[fix]=20CORS=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20WebConfig.java=20=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/backend/pirocheck/config/WebConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/config/WebConfig.java b/backend/pirocheck/src/main/java/backend/pirocheck/config/WebConfig.java index d149b66..430ff99 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/config/WebConfig.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/config/WebConfig.java @@ -10,7 +10,11 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") // 백엔드 API 요청에만 CORS 허용 - .allowedOrigins("http://localhost:5173", "https://www.pirocheck.org") // 프론트 배포 URL + .allowedOrigins( + "http://localhost:5173", + "http://www.pirocheck.org", + "https://www.pirocheck.org" + ) // 프론트 배포 URL .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메서드 .allowedHeaders("*") .allowCredentials(true); // 세션 쿠키 주고받기 허용 From 7724f98f4a1e91c5fce8d13a3938f5f47f55c609 Mon Sep 17 00:00:00 2001 From: qkrxogmla Date: Mon, 19 May 2025 10:53:31 +0900 Subject: [PATCH 21/66] =?UTF-8?q?css=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/admin/AttendanceCode.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/pages/admin/AttendanceCode.module.css b/frontend/src/pages/admin/AttendanceCode.module.css index 09977f1..344ba58 100644 --- a/frontend/src/pages/admin/AttendanceCode.module.css +++ b/frontend/src/pages/admin/AttendanceCode.module.css @@ -36,4 +36,5 @@ justify-content: center; align-items: center; margin-top: 60px; + gap: 20px; } From 047b3e72aa78ab17fd3f11c20327635552066dcf Mon Sep 17 00:00:00 2001 From: qkrxogmla Date: Mon, 19 May 2025 11:04:53 +0900 Subject: [PATCH 22/66] =?UTF-8?q?=20url=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/admin/AttendanceCode.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/admin/AttendanceCode.jsx b/frontend/src/pages/admin/AttendanceCode.jsx index 79e0002..9b9dde0 100644 --- a/frontend/src/pages/admin/AttendanceCode.jsx +++ b/frontend/src/pages/admin/AttendanceCode.jsx @@ -9,7 +9,7 @@ const AttendanceCode = () => { // 출석코드 생성 const generateCode = async () => { try { - const res = await api.post("/attendance/start"); + const res = await api.post("admin/attendance/start"); const newCode = res.data.data.code; setCode(newCode); } catch (error) { @@ -22,7 +22,7 @@ const AttendanceCode = () => { // 출석코드 만료 const expireCode = async () => { try { - const res = await api.put("/attendance/expire-latest"); + const res = await api.put("admin/attendance/expire-latest"); alert(res.data.message || "출석코드가 만료되었습니다"); setCode(""); } catch (error) { From b3ad105026b79bf50de28807bdfb14128dfa90cd Mon Sep 17 00:00:00 2001 From: qkrxogmla Date: Mon, 19 May 2025 11:32:06 +0900 Subject: [PATCH 23/66] =?UTF-8?q?=20=EC=B6=9C=EC=84=9D=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A2=85=EB=A3=8C=20expire=20api=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Header.jsx | 3 ++- frontend/src/pages/admin/AttendanceCode.jsx | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 9530644..1a38ba6 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -17,7 +17,8 @@ const Header = () => { const showRightDeposit = !path.includes("deposit") && !path.includes("managestudent") && - !path.includes("managetask"); // 수강생 관리, 과제 관리 페이지 추가 + !path.includes("managetask") && + !path.includes("attendancecode"); const showRightMagageStudent = path.includes("attendancecode"); diff --git a/frontend/src/pages/admin/AttendanceCode.jsx b/frontend/src/pages/admin/AttendanceCode.jsx index 9b9dde0..8806d0e 100644 --- a/frontend/src/pages/admin/AttendanceCode.jsx +++ b/frontend/src/pages/admin/AttendanceCode.jsx @@ -19,10 +19,12 @@ const AttendanceCode = () => { } }; - // 출석코드 만료 + // 출석코드 만료 (직접 코드 전달 방식) const expireCode = async () => { try { - const res = await api.put("admin/attendance/expire-latest"); + const res = await api.put("admin/attendance/expire", null, { + params: { code }, + }); alert(res.data.message || "출석코드가 만료되었습니다"); setCode(""); } catch (error) { From 1b2996e56e34344b93b934df0a9984752e5ef5ec Mon Sep 17 00:00:00 2001 From: l-wanderer01 Date: Mon, 19 May 2025 21:38:08 +0900 Subject: [PATCH 24/66] =?UTF-8?q?[fix]=20=EA=B3=BC=EC=A0=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20=EC=9E=90=EB=8F=99=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=EC=97=90=EA=B2=8C=20=ED=95=A0=EB=8B=B9?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pirocheck/Assignment/entity/Assignment.java | 1 + .../Assignment/service/AssignmentService.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java index 410c522..eede8f9 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/Assignment.java @@ -37,6 +37,7 @@ public class Assignment { // AssignmentItem 입장에서 "assignment" 필드의 외래 키를 가진 주인 // assignment를 참조하는 assignmentitem 컬랙션을 가짐 @OneToMany(mappedBy = "assignment", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default private List assignments = new ArrayList<>(); // 연관관계 편의 메서드 (양방향 시 자주 사용) diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java index c4c25cd..f1ef8ef 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java @@ -83,6 +83,20 @@ public String createAssignment(AssignmentCreateReq assignmentCreateReq) { assignment = assignmentRepository.save(assignment); + // 전체 유저에게 과제 자동 할당 + List users = userRepository.findAll(); + + for (User user : users) { + + AssignmentItem item = AssignmentItem.create(user, assignment, AssignmentStatus.INSUFFICIENT); + + assignment.addAssignmentItem(item); + user.addAssignmentItem(item); + +// assignmentItemRepository.save(item); +// Cascade 설정이 되어있으므로 assignment = assignmentRepository.save(assignment); 이 코드를 실행할 때 연관된 AssignmentItem도 함께 저장 됨 + } + return assignment.getAssignmentName(); } From a152ef70b4058b1c7cd5500b14b9326d44d03030 Mon Sep 17 00:00:00 2001 From: qkrxogmla Date: Tue, 20 May 2025 02:51:08 +0900 Subject: [PATCH 25/66] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=9B=84?= =?UTF-8?q?=20=EC=A0=91=EA=B7=BC=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.jsx | 83 +++++++++++++++++++++--- frontend/src/components/RequireAdmin.jsx | 12 ++++ frontend/src/components/RequireAuth.jsx | 13 ++++ 3 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/RequireAdmin.jsx create mode 100644 frontend/src/components/RequireAuth.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3f48079..5d442d1 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,6 +11,8 @@ import ManageTask from "./pages/admin/ManageTask.jsx"; import AttendanceCode from "./pages/admin/AttendanceCode"; import Attendance from "./pages/generation/Attendance"; import AdminStudentAttendance from "./pages/admin/AdminStudentAttendance"; +import RequireAuth from "./components/RequireAuth"; +import RequireAdmin from "./components/RequireAdmin"; function App() { return ( @@ -18,15 +20,78 @@ function App() { } /> } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> ); diff --git a/frontend/src/components/RequireAdmin.jsx b/frontend/src/components/RequireAdmin.jsx new file mode 100644 index 0000000..becbb4d --- /dev/null +++ b/frontend/src/components/RequireAdmin.jsx @@ -0,0 +1,12 @@ +import { Navigate } from "react-router-dom"; + +const RequireAdmin = ({ children }) => { + const user = JSON.parse(localStorage.getItem("user")); + + if (!user) return ; + if (user.role !== "ADMIN") return ; + + return children; +}; + +export default RequireAdmin; diff --git a/frontend/src/components/RequireAuth.jsx b/frontend/src/components/RequireAuth.jsx new file mode 100644 index 0000000..8a61988 --- /dev/null +++ b/frontend/src/components/RequireAuth.jsx @@ -0,0 +1,13 @@ +import { Navigate } from "react-router-dom"; + +const RequireAuth = ({ children }) => { + const user = JSON.parse(localStorage.getItem("user")); + + if (!user) { + return ; + } + + return children; +}; + +export default RequireAuth; From 905df9cb9bdcf2d90b59f1382071fd330f333d02 Mon Sep 17 00:00:00 2001 From: qkrxogmla Date: Tue, 20 May 2025 03:05:43 +0900 Subject: [PATCH 26/66] =?UTF-8?q?admin=EC=95=84=EB=8B=88=EB=A9=B4login?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/RequireAdmin.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/RequireAdmin.jsx b/frontend/src/components/RequireAdmin.jsx index becbb4d..ef87dff 100644 --- a/frontend/src/components/RequireAdmin.jsx +++ b/frontend/src/components/RequireAdmin.jsx @@ -4,7 +4,7 @@ const RequireAdmin = ({ children }) => { const user = JSON.parse(localStorage.getItem("user")); if (!user) return ; - if (user.role !== "ADMIN") return ; + if (user.role !== "ADMIN") return ; return children; }; From f274e00176fe82787bda7deba4637c2acb771580 Mon Sep 17 00:00:00 2001 From: qkrxogmla Date: Tue, 20 May 2025 03:10:23 +0900 Subject: [PATCH 27/66] =?UTF-8?q?=EC=9B=B9=EC=82=AC=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=ED=8B=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 79c4701..eadd9cb 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,9 @@ - + - Vite + React + pirocheck
From cbe231196e44fbe72c64330d72c00ae7f6308f30 Mon Sep 17 00:00:00 2001 From: l-wanderer01 Date: Tue, 20 May 2025 17:53:28 +0900 Subject: [PATCH 28/66] =?UTF-8?q?[fix]=20=EA=B3=BC=EC=A0=9C=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pirocheck/Assignment/service/AssignmentService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java index f1ef8ef..398e662 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java @@ -14,6 +14,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; @@ -22,6 +23,7 @@ import java.util.Map; import java.util.stream.Collectors; +@Slf4j // 로그를 찍기위해 사용 @Service @Transactional @RequiredArgsConstructor @@ -118,7 +120,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("조회된 사용자가 없습니다.")); From a53a7221f36b1b55dc89a076db411732a83f19c7 Mon Sep 17 00:00:00 2001 From: l-wanderer01 Date: Tue, 20 May 2025 18:28:39 +0900 Subject: [PATCH 29/66] =?UTF-8?q?[feat]=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EA=B3=BC=EC=A0=9C=20=EC=B1=84=EC=A0=90=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AssignmentController.java | 8 +++++--- .../dto/request/AssignmentItemUpdateReq.java | 16 ++++++++++++++++ .../Assignment/entity/AssignmentItem.java | 6 ++++++ .../repository/AssignmentItemRepository.java | 5 +++++ .../Assignment/service/AssignmentService.java | 19 +++++++++++++++++++ 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentItemUpdateReq.java diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java index 2759f31..042c94d 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/controller/AssignmentController.java @@ -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; @@ -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); } } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentItemUpdateReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentItemUpdateReq.java new file mode 100644 index 0000000..b917d54 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/dto/request/AssignmentItemUpdateReq.java @@ -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; +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/AssignmentItem.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/AssignmentItem.java index 74719c7..6a9dddf 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/AssignmentItem.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/entity/AssignmentItem.java @@ -35,4 +35,10 @@ public static AssignmentItem create(User user, Assignment assignment, Assignment .submitted(submitted) .build(); } + + public void update(AssignmentStatus submitted) { + + this.submitted = submitted; + + } } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/repository/AssignmentItemRepository.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/repository/AssignmentItemRepository.java index 1a39eec..f07c351 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/repository/AssignmentItemRepository.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/repository/AssignmentItemRepository.java @@ -1,5 +1,6 @@ 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; @@ -7,6 +8,7 @@ import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; @Repository public interface AssignmentItemRepository extends JpaRepository { @@ -17,4 +19,7 @@ public interface AssignmentItemRepository extends JpaRepository findByUserAndAssignment(User user, Assignment assignment); + // Optional 처리로 오류 발생 check + } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java index 398e662..e589e2c 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Assignment/service/AssignmentService.java @@ -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; @@ -139,4 +140,22 @@ public AssignmentStatus createAssignmentItem(Long userId, Long assignmentId, 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(); + } } From f1f223ec4a874e6f36f189ffe795b658b347758c Mon Sep 17 00:00:00 2001 From: dietken1 Date: Tue, 20 May 2025 21:22:17 +0900 Subject: [PATCH 30/66] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminAttendanceController.java | 52 ++++++++++++++++++- .../request/UpdateAttendanceStatusReq.java | 20 +++++++ .../dto/response/UserAttendanceStatusRes.java | 34 ++++++++++++ .../repository/AttendanceRepository.java | 3 ++ .../Attendance/service/AttendanceService.java | 41 ++++++++++++++- 5 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java create mode 100644 backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/UserAttendanceStatusRes.java diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java index f811f53..5b75a14 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java @@ -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; @@ -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 @@ -79,7 +84,7 @@ public ApiResponse getActiveCode() { public ApiResponse 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); @@ -104,4 +109,49 @@ public ApiResponse 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 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> getAllAttendanceByDateAndOrder( + @Parameter(description = "조회할 날짜 (YYYY-MM-DD)", required = true) + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, + @Parameter(description = "조회할 차수", required = true) + @RequestParam int order) { + + List attendances = attendanceService.findAllByDateAndOrder(date, order); + return ApiResponse.success(attendances); + } } \ No newline at end of file diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java new file mode 100644 index 0000000..2c6973c --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java @@ -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; +} \ No newline at end of file diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/UserAttendanceStatusRes.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/UserAttendanceStatusRes.java new file mode 100644 index 0000000..fcb624c --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/UserAttendanceStatusRes.java @@ -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; +} \ No newline at end of file diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/repository/AttendanceRepository.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/repository/AttendanceRepository.java index e06f6e9..783b90b 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/repository/AttendanceRepository.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/repository/AttendanceRepository.java @@ -17,4 +17,7 @@ public interface AttendanceRepository extends JpaRepository { // 출석 실패 int countByUserAndStatusFalse(User user); + + // 특정 날짜와 차수에 대한 모든 출석 기록 조회 + List findByDateAndOrder(LocalDate date, int order); } diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java index ae2d5a8..4a1adb8 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java @@ -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; @@ -104,7 +105,7 @@ public String expireLatestAttendanceCode() { // 출석코드 만료처리 함수 @Transactional - public String exprireAttendanceCode(String code) { + public String expireAttendanceCode(String code) { Optional codeOpt = attendanceCodeRepository.findByCodeAndDate(code, LocalDate.now()); if (codeOpt.isEmpty()) { @@ -208,4 +209,42 @@ public List findByUserIdAndDate(Long userId, LocalDate date) .sorted(Comparator.comparingInt(AttendanceSlotRes::getOrder)) .toList(); } + + // 관리자가 유저의 출석 상태를 변경하는 함수 + @Transactional + public boolean updateAttendanceStatus(Long attendanceId, boolean status) { + Optional attendanceOpt = attendanceRepository.findById(attendanceId); + + if (attendanceOpt.isEmpty()) { + return false; + } + + // 출석 상태 변경 + Attendance attendance = attendanceOpt.get(); + attendance.setStatus(status); + attendanceRepository.save(attendance); + return true; + } + + // 특정 날짜와 차수의 모든 학생 출석 현황 조회 + public List findAllByDateAndOrder(LocalDate date, int order) { + // 해당 날짜와 차수에 대한 모든 출석 기록 조회 + List 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(); + } } From 861a5e449c830f763b3e23af158918f165d3b0a4 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Wed, 21 May 2025 11:28:55 +0900 Subject: [PATCH 31/66] =?UTF-8?q?[fix]:=20ManageStudent.jsx=20css=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/admin/ManageStudent.module.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/pages/admin/ManageStudent.module.css b/frontend/src/pages/admin/ManageStudent.module.css index 931680a..3a900c7 100644 --- a/frontend/src/pages/admin/ManageStudent.module.css +++ b/frontend/src/pages/admin/ManageStudent.module.css @@ -25,6 +25,9 @@ text-align: left; font-size: 16px; width: 100%; + display: flex; + align-items: center; + justify-content: space-between; } .student_button:hover { border: 1px solid #39ff14; From 296161b61417980f9c8eb838523f1e211f8a2b9d Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Wed, 21 May 2025 12:03:19 +0900 Subject: [PATCH 32/66] =?UTF-8?q?[add]:=20DetailManageStudent.jsx=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C,=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.jsx | 9 ++++ frontend/src/api/students.js | 10 ++++ .../src/pages/admin/DetailManageStudent.jsx | 47 +++++++++++++++++++ .../admin/DetailManageStudent.module.css | 11 +++++ frontend/src/pages/admin/ManageStudent.jsx | 8 +++- 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/admin/DetailManageStudent.jsx create mode 100644 frontend/src/pages/admin/DetailManageStudent.module.css diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5d442d1..5bc8878 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -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"; @@ -68,6 +69,14 @@ function App() { } /> + + + + } + /> { }); 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; + } +}; diff --git a/frontend/src/pages/admin/DetailManageStudent.jsx b/frontend/src/pages/admin/DetailManageStudent.jsx new file mode 100644 index 0000000..f932ea9 --- /dev/null +++ b/frontend/src/pages/admin/DetailManageStudent.jsx @@ -0,0 +1,47 @@ +import { useParams } from "react-router-dom"; +import { useEffect, useState } from "react"; +import Header from "../../components/Header"; +import style from "./DetailManageStudent.module.css"; +import { getStudentDetail } from "../../api/students"; + +const DetailManageStudent = () => { + const { studentId } = useParams(); + const [student, setStudent] = useState(null); + + useEffect(() => { + const fetchStudent = async () => { + try { + const data = await getStudentDetail(studentId); + setStudent(data); + } catch (err) { + console.error("학생 상세 정보 불러오기 실패:", err); + } + }; + + fetchStudent(); + }, [studentId]); + + if (!student) return
loading...
; + + return ( +
+
+
+
+

{student.name}

+

잔여 보증금: {student.deposit}원

+

보증금 방어권: {student.defence}

+
+ +
+ {student.assignmentTitles.map((title, idx) => ( + + ))} +
+
+
+ ); +}; +export default DetailManageStudent; diff --git a/frontend/src/pages/admin/DetailManageStudent.module.css b/frontend/src/pages/admin/DetailManageStudent.module.css new file mode 100644 index 0000000..86fe69d --- /dev/null +++ b/frontend/src/pages/admin/DetailManageStudent.module.css @@ -0,0 +1,11 @@ +.managestudent_wrapper { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; +} +.under_header { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/frontend/src/pages/admin/ManageStudent.jsx b/frontend/src/pages/admin/ManageStudent.jsx index 252175f..80a02e3 100644 --- a/frontend/src/pages/admin/ManageStudent.jsx +++ b/frontend/src/pages/admin/ManageStudent.jsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; import { getStudentsByName } from "../../api/students"; import Header from "../../components/Header"; import InputBlock from "../../components/InputBlock"; @@ -8,6 +9,7 @@ const ManageStudent = () => { const [studentName, setStudentName] = useState([""]); const [page, setPage] = useState(1); const [students, setStudents] = useState([]); // 서버 데이터 저장 + const navigate = useNavigate(); const studentsPerPage = 6; @@ -54,7 +56,11 @@ const ManageStudent = () => { />
{paginatedStudents.map((student, index) => ( - ))} From c7f6dd8c886f67558f3c893a4c6c968dec25986b Mon Sep 17 00:00:00 2001 From: Imggaggu Date: Wed, 21 May 2025 19:48:34 +0900 Subject: [PATCH 33/66] =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=A0=84=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.jsx | 3 +- .../pages/admin/AdminStudentAssignment.jsx | 125 ++++++++++++++++++ .../admin/AdminStudentAssignment.module.css | 112 ++++++++++++++++ 3 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/admin/AdminStudentAssignment.jsx create mode 100644 frontend/src/pages/admin/AdminStudentAssignment.module.css diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ed53cf3..e483d0a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,7 +11,7 @@ import ManageTask from "./pages/admin/ManageTask.jsx"; import AttendanceCode from "./pages/admin/AttendanceCode"; import Attendance from "./pages/generation/Attendance"; import AdminStudentAttendance from "./pages/admin/AdminStudentAttendance"; - +import AdminStudentAssignment from "./pages/admin/AdminStudentAssignment.jsx"; function App() { return ( @@ -27,6 +27,7 @@ function App() { } /> } /> } /> + } /> ); diff --git a/frontend/src/pages/admin/AdminStudentAssignment.jsx b/frontend/src/pages/admin/AdminStudentAssignment.jsx new file mode 100644 index 0000000..2114cb4 --- /dev/null +++ b/frontend/src/pages/admin/AdminStudentAssignment.jsx @@ -0,0 +1,125 @@ +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import AdminStudentHeader from "../../components/AdminStudentHeader"; +import WeeklyOpenBlock from "../../components/WeeklyOpenBlock"; +import AssignmentInfoBlock from "../../components/AssignmentInfoBlock"; +import api from "../../api/api"; +import styles from "./AdminStudentAssignment.module.css"; + +const AdminStudentAssignment = () => { + const { studentId, week } = useParams(); + const [studentInfo, setStudentInfo] = useState(null); + const [weeks, setWeeks] = useState([]); + const [highlightCard, setHighlightCard] = useState(null); + const [selectedWeekLabel, setSelectedWeekLabel] = useState(null); + + useEffect(() => { + api.get(`/admin/users/${studentId}`).then((res) => { + setStudentInfo(res.data.data); + }); + + api + .get(`/admin/managestudent/{studentId}`, { + params: { userId: studentId }, + withCredentials: true, + }) + .then((res) => { + const formatted = res.data.data.map((weekItem) => ({ + week: weekItem.week, + label: `${weekItem.week}주차 ${weekItem.title}`, + days: weekItem.days.map((dayItem) => ({ + day: dayItem.day, + subject: weekItem.title, + tasks: dayItem.details.map((task) => ({ + id: task.id, + label: task.assignmentName, + status: task.status, + modified: false, + })), + })), + })); + + setWeeks(formatted); + + const matched = formatted.find((w) => String(w.week) === String(week)); + if (matched) { + setSelectedWeekLabel(matched.label); + if (matched.days.length > 0) { + setHighlightCard({ + weekLabel: matched.label, + day: matched.days[0].day, + tasks: matched.days[0].tasks, + }); + } + } + }); + }, [studentId, week]); + + const handleStatusChange = (weekIdx, dayIdx, taskIdx, newStatus) => { + const updated = [...weeks]; + const task = updated[weekIdx].days[dayIdx].tasks[taskIdx]; + task.status = newStatus; + task.modified = true; + setWeeks(updated); + }; + + const handleSave = async (taskId, status) => { + await api.put("/admin/assignment/status", { + assignmentId: taskId, + status, + }); + }; + + return ( +
+ window.history.back()} + /> + + {highlightCard && ( +
+ +
+ )} + +
+ {weeks.map((weekItem, weekIdx) => ( +
+

{weekItem.label}

+ {weekItem.days.map((dayItem, dayIdx) => ( +
+

{dayItem.day}   {dayItem.subject}

+
+ {dayItem.tasks.map((task, taskIdx) => ( +
+ {task.label} + + +
+ ))} +
+ +
+ ))} +
+ ))} +
+
+ ); +}; + +export default AdminStudentAssignment; \ No newline at end of file diff --git a/frontend/src/pages/admin/AdminStudentAssignment.module.css b/frontend/src/pages/admin/AdminStudentAssignment.module.css new file mode 100644 index 0000000..04e3e68 --- /dev/null +++ b/frontend/src/pages/admin/AdminStudentAssignment.module.css @@ -0,0 +1,112 @@ +.container { + display: flex; + flex-direction: column; + padding: 20px; + font-family: "Inter", sans-serif; + color: white; + background-color: #1e1e1e; +} + +/* 과제 개요 카드 (상단 형광 카드) */ +.info { + background-color: #045e07; + border-radius: 12px; + padding: 16px; + margin-bottom: 24px; +} + +/* 주차별 목록 */ +.weekList { + display: flex; + flex-direction: column; + gap: 24px; +} + +/* 주차 구간 */ +.weekBlock { + border-left: 4px solid #00c851; + background-color: #2d2d2d; + border-radius: 10px; + padding: 16px; +} + +.weekTitle { + font-size: 18px; + font-weight: bold; + color: #00ff99; + margin-bottom: 12px; +} + +/* 요일별 카드 */ +.dayCard { + background-color: #3a3a3a; + padding: 12px 16px; + border-radius: 10px; + margin-bottom: 16px; +} + +.dayLabel { + font-size: 16px; + font-weight: 600; + margin-bottom: 8px; +} + +/* 개별 과제 */ +.taskList { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 12px; +} + +.taskRow { + display: flex; + align-items: center; + gap: 10px; + background-color: #505050; + border-radius: 6px; + padding: 8px 12px; +} + +.taskLabel { + flex: 1; + font-size: 14px; + color: white; +} + +/* 드롭다운 */ +.taskRow select { + padding: 6px 8px; + border-radius: 6px; + background-color: #2a2a2a; + color: white; + border: 1px solid #777; +} + +/* save 버튼 */ +.saveButton { + padding: 4px 10px; + border-radius: 6px; + background-color: #00c851; + color: white; + font-weight: bold; + border: none; + cursor: pointer; +} + +.saveButton:disabled { + background-color: gray; + cursor: not-allowed; +} + +/* submit 버튼 */ +.submitBtn { + margin-top: 8px; + padding: 6px 12px; + border-radius: 8px; + background-color: #1fa067; + color: white; + font-weight: bold; + border: none; + cursor: pointer; +} From 4b35e53ac425968e1e001d7bd284fd3d6396c255 Mon Sep 17 00:00:00 2001 From: Imggaggu Date: Wed, 21 May 2025 20:37:07 +0900 Subject: [PATCH 34/66] =?UTF-8?q?[Feat]=20admin=20attendance=20student=20a?= =?UTF-8?q?pi=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/adminattendance.js | 26 ++++++++++++++++++ .../pages/admin/AdminStudentAttendance.jsx | 27 +++++++++---------- 2 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 frontend/src/api/adminattendance.js diff --git a/frontend/src/api/adminattendance.js b/frontend/src/api/adminattendance.js new file mode 100644 index 0000000..4506980 --- /dev/null +++ b/frontend/src/api/adminattendance.js @@ -0,0 +1,26 @@ +import api from "./api"; + +// api/attendanceApi.js + +export const getStudentBasicInfo = async (studentId) => { + try { + const res = await api.get(`/admin/managestudent/${studentId}`); + return res.data; + } catch (error) { + console.error("학생 기본 정보 불러오기 실패:", error); + throw error; + } +}; + +export const getStudentAttendance = async (studentId) => { + try { + const res = await api.get("/admin/attendance/user", { + params: { userId: studentId }, + withCredentials: true, + }); + return res.data; + } catch (error) { + console.error("학생 출석 정보 불러오기 실패:", error); + throw error; + } +}; diff --git a/frontend/src/pages/admin/AdminStudentAttendance.jsx b/frontend/src/pages/admin/AdminStudentAttendance.jsx index 827248b..6eed5d1 100644 --- a/frontend/src/pages/admin/AdminStudentAttendance.jsx +++ b/frontend/src/pages/admin/AdminStudentAttendance.jsx @@ -5,6 +5,7 @@ import DailyAttendanceCard from "../../components/AdminDailyAttendanceCard"; import api from "../../api/api"; import styles from "./AdminStudentAttendance.module.css"; import AdminWeeklyAttendanceList from "../../components/AdminWeeklyAttendanceList"; +import { getStudentBasicInfo, getStudentAttendance } from "../../api/adminattendance"; const AdminStudentAttendance = () => { const { studentId } = useParams(); @@ -13,22 +14,20 @@ const AdminStudentAttendance = () => { const [selectedDate, setSelectedDate] = useState(null); useEffect(() => { - // 1. 학생 정보 가져오기 - api.get(`/admin/users/${studentId}`).then((res) => { - setStudentInfo(res.data.data); - }); + const fetchData = async () => { + try { + const studentRes = await getStudentBasicInfo(studentId); + setStudentInfo(studentRes.data); - // 2. 주차별 출석 데이터 가공 - api - .get("/admin/attendance/user", { - params: { userId: studentId }, - withCredentials: true, - }) - .then((res) => { - const raw = res.data.data; - const processed = processWeeklyAttendance(raw); + const attendanceRes = await getStudentAttendance(studentId); + const processed = processWeeklyAttendance(attendanceRes.data); setAttendanceData(processed); - }); + } catch (err) { + console.error("데이터 불러오기 실패:", err); + } + }; + + fetchData(); }, [studentId]); /* From cfef8c897d98bd30a2d54ea22430afe7a295e4c6 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Wed, 21 May 2025 21:11:24 +0900 Subject: [PATCH 35/66] =?UTF-8?q?[fix]:=20DetailManageStudent.jsx=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/api.js | 2 -- .../src/pages/admin/DetailManageStudent.jsx | 8 +++++-- frontend/src/pages/admin/ManageStudent.jsx | 21 +++++++++++-------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js index 60d0af3..1950026 100644 --- a/frontend/src/api/api.js +++ b/frontend/src/api/api.js @@ -2,10 +2,8 @@ import axios from "axios"; const api = axios.create({ baseURL: "http://api.pirocheck.org:8080/api", - // 수정 필요한지 재검 필요함 // "http://api.pirocheck.org:8080/api" - withCredentials: true, }); diff --git a/frontend/src/pages/admin/DetailManageStudent.jsx b/frontend/src/pages/admin/DetailManageStudent.jsx index f932ea9..70266a2 100644 --- a/frontend/src/pages/admin/DetailManageStudent.jsx +++ b/frontend/src/pages/admin/DetailManageStudent.jsx @@ -6,12 +6,13 @@ import { getStudentDetail } from "../../api/students"; const DetailManageStudent = () => { const { studentId } = useParams(); + const numericId = Number(studentId); const [student, setStudent] = useState(null); useEffect(() => { const fetchStudent = async () => { try { - const data = await getStudentDetail(studentId); + const data = await getStudentDetail(numericId); setStudent(data); } catch (err) { console.error("학생 상세 정보 불러오기 실패:", err); @@ -19,10 +20,13 @@ const DetailManageStudent = () => { }; fetchStudent(); - }, [studentId]); + }, [numericId]); if (!student) return
loading...
; + console.log("studentId from URL:", studentId); + console.log("numericId:", numericId); + return (
diff --git a/frontend/src/pages/admin/ManageStudent.jsx b/frontend/src/pages/admin/ManageStudent.jsx index 80a02e3..525a021 100644 --- a/frontend/src/pages/admin/ManageStudent.jsx +++ b/frontend/src/pages/admin/ManageStudent.jsx @@ -55,15 +55,18 @@ const ManageStudent = () => { onChange={handleChange} />
- {paginatedStudents.map((student, index) => ( - - ))} + {paginatedStudents.map((student, index) => { + console.log("student to show:", student); // 🔍 여기 추가 + return ( + + ); + })}
{students.length > studentsPerPage && ( From b399b35bbe34d7cead94dd7b45db5248a7ea1338 Mon Sep 17 00:00:00 2001 From: Imggaggu Date: Wed, 21 May 2025 21:20:24 +0900 Subject: [PATCH 36/66] =?UTF-8?q?[Fix]=20assignment.js=20api=20=EC=9E=AC?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/assignment.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/api/assignment.js b/frontend/src/api/assignment.js index cf1a11a..323730a 100644 --- a/frontend/src/api/assignment.js +++ b/frontend/src/api/assignment.js @@ -1,6 +1,16 @@ import api from "./api"; - +/* export const fetchAssignmentsByUser = async (userId) => { const res = await api.get(`/assignment/grouped/${userId}`); return res.data; }; +*/ +export const fetchAssignmentsByUser = async (userId) => { + try { + const res = await api.get(`/api/assignment/${userId}`); + return res.data; // 백엔드가 반환하는 JSON 그대로 + } catch (err) { + console.error("과제 데이터 불러오기 실패:", err); + throw err; + } +}; \ No newline at end of file From 94a8a5220c1e48b3b6b267642aee88eb02737bd2 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Wed, 21 May 2025 21:27:17 +0900 Subject: [PATCH 37/66] =?UTF-8?q?[add]:=20Detail=5Fgreenbox=20UI=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/admin/DetailManageStudent.jsx | 15 +++++++++---- .../admin/DetailManageStudent.module.css | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/admin/DetailManageStudent.jsx b/frontend/src/pages/admin/DetailManageStudent.jsx index 70266a2..24ca910 100644 --- a/frontend/src/pages/admin/DetailManageStudent.jsx +++ b/frontend/src/pages/admin/DetailManageStudent.jsx @@ -32,11 +32,18 @@ const DetailManageStudent = () => {
-

{student.name}

-

잔여 보증금: {student.deposit}원

-

보증금 방어권: {student.defence}

+

{student.name}

+
+

+ 잔여 보증금:

{student.deposit}원

+

+
+
+

+ 보증금 방어권:

{student.defence}

+

+
-
{student.assignmentTitles.map((title, idx) => ( - ) : ( -
- )} + ) : null} {showRightMagageStudent ? (
+
{student.assignmentTitles.map((title, idx) => (
- {student.assignmentTitles.map((title, idx) => ( - ))}
From a5c9d1183c54a5cb8394743b49bd9186855f121f Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Thu, 22 May 2025 02:11:44 +0900 Subject: [PATCH 58/66] =?UTF-8?q?[add]:=20=EB=B2=84=ED=8A=BC=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=EB=B6=80=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/admin/DetailManageStudent.jsx | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/frontend/src/pages/admin/DetailManageStudent.jsx b/frontend/src/pages/admin/DetailManageStudent.jsx index 69a9074..e6b20bb 100644 --- a/frontend/src/pages/admin/DetailManageStudent.jsx +++ b/frontend/src/pages/admin/DetailManageStudent.jsx @@ -50,23 +50,27 @@ const DetailManageStudent = () => { 보증금 방어권: {student.defence}
- -
- {weekData.map((week, index) => ( - - ))} -
+ {student && ( + + )} + {student && ( +
+ {weekData.map((week, index) => ( + + ))} +
+ )}
); From aadcc8962d0cdeb7cf9bb5a8f906911dbf76577f Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Thu, 22 May 2025 15:30:22 +0900 Subject: [PATCH 59/66] =?UTF-8?q?[fix]:=20ManagaTask.jsx=20save=5Fbtn=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=A7=81=20=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/TaskModal.jsx | 26 ++++++++++++++++++- .../src/pages/admin/DetailManageStudent.jsx | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/TaskModal.jsx b/frontend/src/components/TaskModal.jsx index b3a3b6b..a1d9879 100644 --- a/frontend/src/components/TaskModal.jsx +++ b/frontend/src/components/TaskModal.jsx @@ -1,5 +1,6 @@ import { useState } from "react"; import styles from "../pages/admin/ManageTask.module.css"; +import api from "../api/api"; const TaskModal = ({ weekInfo, onClose }) => { const [topic, setTopic] = useState(""); @@ -16,6 +17,27 @@ const TaskModal = ({ weekInfo, onClose }) => { setTaskList([...taskList, ""]); }; + const handleSave = async () => { + const requests = taskList.map((task, index) => + api.post("/admin/assignment/signup", { + subject: topic, + assignmentName: task, + week: parseInt(weekInfo.week), + day: day, + orderNumber: index + 1, + }) + ); + + try { + await Promise.all(requests); + alert("과제가 저장되었습니다."); + onClose(); + } catch (error) { + console.error("저장 오류:", error); + alert("과제 저장 중 오류가 발생했습니다."); + } + }; + return (
@@ -52,7 +74,9 @@ const TaskModal = ({ weekInfo, onClose }) => {
- +
diff --git a/frontend/src/pages/admin/DetailManageStudent.jsx b/frontend/src/pages/admin/DetailManageStudent.jsx index e6b20bb..632aa59 100644 --- a/frontend/src/pages/admin/DetailManageStudent.jsx +++ b/frontend/src/pages/admin/DetailManageStudent.jsx @@ -23,6 +23,7 @@ const DetailManageStudent = () => { const fetchStudent = async () => { try { const data = await getStudentDetail(numericId); + console.log("API 응답 데이터:", data); // 확인 포인트 setStudent(data); } catch (err) { console.error("학생 상세 정보 불러오기 실패:", err); From 580a01d4d7334665d7af9cd8dfacdfd8c92eb9d3 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Thu, 22 May 2025 16:04:36 +0900 Subject: [PATCH 60/66] =?UTF-8?q?[fix]:=20ManagaTask.jsx=20save=5Fbtn=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=A7=81=20=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/TaskModal.jsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/TaskModal.jsx b/frontend/src/components/TaskModal.jsx index a1d9879..7bac438 100644 --- a/frontend/src/components/TaskModal.jsx +++ b/frontend/src/components/TaskModal.jsx @@ -18,15 +18,28 @@ const TaskModal = ({ weekInfo, onClose }) => { }; const handleSave = async () => { - const requests = taskList.map((task, index) => - api.post("/admin/assignment/signup", { + console.log("save clicked"); + + const weekNumber = parseInt(weekInfo.week.replace("주차", "")); // 주차 숫자 정보만 추출 + const filteredTasks = taskList.filter((t) => t.trim() !== ""); // 빈 값 제거 + + const requests = filteredTasks.map((task, index) => { + console.log("sending:", { + subject: topic, + assignmentName: task, + week: weekNumber, + day: day, + orderNumber: index + 1, + }); + + return api.post("/admin/assignment/signup", { subject: topic, assignmentName: task, - week: parseInt(weekInfo.week), + week: weekNumber, day: day, orderNumber: index + 1, - }) - ); + }); + }); try { await Promise.all(requests); From f14a4921e4a3932aacae517ce5577da7faa7ccae Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Thu, 22 May 2025 16:32:32 +0900 Subject: [PATCH 61/66] =?UTF-8?q?[fix]:=20ManagaTask.jsx=20save=5Fbtn=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=A7=81=20=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/TaskModal.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/TaskModal.jsx b/frontend/src/components/TaskModal.jsx index 7bac438..a2e7a42 100644 --- a/frontend/src/components/TaskModal.jsx +++ b/frontend/src/components/TaskModal.jsx @@ -42,7 +42,8 @@ const TaskModal = ({ weekInfo, onClose }) => { }); try { - await Promise.all(requests); + const response = await Promise.all(requests); + console.log("응답들: ", response); alert("과제가 저장되었습니다."); onClose(); } catch (error) { From f9850f833fad28b330d3b6fe316b262642ca5fae Mon Sep 17 00:00:00 2001 From: qkrxogmla Date: Thu, 22 May 2025 16:47:29 +0900 Subject: [PATCH 62/66] =?UTF-8?q?=EC=97=94=ED=84=B0=EC=8B=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/Login.jsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/Login.jsx b/frontend/src/Login.jsx index 58f63b1..4690e4d 100644 --- a/frontend/src/Login.jsx +++ b/frontend/src/Login.jsx @@ -57,7 +57,14 @@ const Login = () => { return (
-
+
{ + if (e.key === "Enter" && name && password) { + handleLogin(); + } + }} + >

PIROCHECK

{ placeholder: "비밀번호", }, ]} - values={[name, password]} // InputBlock props 수정에 따라 추가 + values={[name, password]} onChange={handleChange} />
From 6d80ef436f7505db696d446ccf50a3c10c09df9e Mon Sep 17 00:00:00 2001 From: dietken1 Date: Thu, 22 May 2025 22:03:32 +0900 Subject: [PATCH 63/66] =?UTF-8?q?fix:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EC=B6=9C=EC=84=9D=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminAttendanceController.java | 179 +++++++++++------- .../request/UpdateAttendanceStatusReq.java | 3 - .../Attendance/service/AttendanceService.java | 78 ++++++++ 3 files changed, 190 insertions(+), 70 deletions(-) diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java index 5b75a14..591b212 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/controller/AdminAttendanceController.java @@ -23,7 +23,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/admin/attendance") +@RequestMapping("/api") @Tag(name = "관리자 출석관리", description = "관리자용 출석 관리 API") public class AdminAttendanceController { @@ -31,127 +31,172 @@ public class AdminAttendanceController { // 출석체크 시작 @Operation(summary = "출석 체크 시작", description = "새로운 출석 코드를 생성하고 출석 체크를 시작합니다.") - @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "200", - description = "출석 코드 생성 성공", - content = @Content(schema = @Schema(implementation = AttendanceCodeResponse.class)) - ), + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "출석 코드 생성 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청") }) - @PostMapping("/start") - public ApiResponse startAttendance() { + @PostMapping("/admin/attendance/start") + public AttendanceCodeResponse startAttendance() { try { AttendanceCode code = attendanceService.generateCodeAndCreateAttendances(); - return ApiResponse.success(AttendanceCodeResponse.from(code)); + return AttendanceCodeResponse.from(code); } catch (IllegalStateException e) { // 하루 최대 출석 체크 횟수를 초과한 경우 - return ApiResponse.error(e.getMessage()); + throw new IllegalStateException(e.getMessage()); } catch (Exception e) { - return ApiResponse.error("출석 코드 생성 중 오류가 발생했습니다: " + e.getMessage()); + throw new RuntimeException("출석 코드 생성 중 오류가 발생했습니다: " + e.getMessage()); } } // 현재 활성화된 출석코드 조회 @Operation(summary = "현재 활성화된 출석 코드 조회", description = "현재 활성화된 출석 코드 정보를 조회합니다.") - @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "200", - description = "조회 성공", - content = @Content(schema = @Schema(implementation = AttendanceCodeResponse.class)) - ), + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "활성화된 출석 코드 없음") }) - @GetMapping("/active-code") - public ApiResponse getActiveCode() { + @GetMapping("/admin/attendance/active-code") + public AttendanceCodeResponse getActiveCode() { Optional codeOpt = attendanceService.getActiveAttendanceCode(); if (codeOpt.isEmpty()) { - return ApiResponse.error("현재 활성화된 출석코드가 없습니다"); + throw new RuntimeException("현재 활성화된 출석코드가 없습니다"); } - return ApiResponse.success(AttendanceCodeResponse.from(codeOpt.get())); + return AttendanceCodeResponse.from(codeOpt.get()); } // 출석체크 종료 (코드 직접 전달) @Operation(summary = "특정 출석 코드 만료", description = "특정 출석 코드를 만료 처리합니다.") - @ApiResponses({ + @ApiResponses(value = { @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("/expire") - public ApiResponse expireAttendance( - @Parameter(description = "만료할 출석 코드", required = true) + @PutMapping("/admin/attendance/expire") + public String expireAttendance( + @Parameter(description = "만료할 출석 코드", example = "1234") @RequestParam String code) { - String result = attendanceService.expireAttendanceCode(code); - - if (result.equals("출석 코드가 성공적으로 만료되었습니다")) { - return ApiResponse.success(result, null); - } else { - return ApiResponse.error(result); - } + return attendanceService.expireAttendanceCode(code); } // 출석체크 종료 (가장 최근 활성화된 코드 자동 만료) @Operation(summary = "최근 활성화된 출석 코드 만료", description = "가장 최근 활성화된 출석 코드를 자동으로 만료 처리합니다.") - @ApiResponses({ + @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "만료 처리 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "활성화된 출석 코드가 없음") }) - @PutMapping("/expire-latest") - public ApiResponse expireLatestAttendance() { - String result = attendanceService.expireLatestAttendanceCode(); - - if (result.equals("출석 코드가 성공적으로 만료되었습니다")) { - return ApiResponse.success(result, null); - } else { - return ApiResponse.error(result); - } + @PutMapping("/admin/attendance/expire-latest") + public String expireLatestAttendance() { + return attendanceService.expireLatestAttendanceCode(); } // 출석 상태 변경 (관리자 전용) @Operation(summary = "출석 상태 변경", description = "관리자가 특정 사용자의 출석 상태를 변경합니다.") - @ApiResponses({ + @ApiResponses(value = { @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 updateAttendanceStatus( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "출석 상태 변경 요청", - required = true, - content = @Content(schema = @Schema(implementation = UpdateAttendanceStatusReq.class)) - ) + @PutMapping("/admin/users/{userId}/attendance/{attendanceId}/status") + public boolean updateAttendanceStatus( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "출석 ID", example = "1") + @PathVariable Long attendanceId, @RequestBody UpdateAttendanceStatusReq req) { - boolean result = attendanceService.updateAttendanceStatus( - req.getAttendanceId(), - req.isStatus() - ); + // userId 파라미터 검증은 여기서 할 수 있음 (필요 시) + return attendanceService.updateAttendanceStatus(attendanceId, req.isStatus()); + } + + // 출석 기록 삭제 (관리자 전용) + @Operation(summary = "출석 기록 삭제", description = "관리자가 특정 사용자의 출석 기록을 삭제합니다.") + @ApiResponses(value = { + @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 = "출석 기록을 찾을 수 없음") + }) + @DeleteMapping("/admin/users/{userId}/attendance/{attendanceId}") + public boolean deleteAttendance( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "출석 ID", example = "1") + @PathVariable Long attendanceId) { - if (result) { - return ApiResponse.success("출석 상태가 성공적으로 변경되었습니다", null); - } else { - return ApiResponse.error("출석 상태 변경에 실패했습니다. 출석 기록을 찾을 수 없습니다."); - } + // userId 파라미터 검증은 여기서 할 수 있음 (필요 시) + return attendanceService.deleteAttendance(attendanceId); } // 특정 날짜와 차수에 대한 모든 학생의 출석 현황 조회 @Operation(summary = "특정 날짜와 차수의 출석 현황 조회", description = "특정 날짜와 차수에 대한 모든 학생의 출석 현황을 조회합니다.") - @ApiResponses({ + @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청") }) - @GetMapping("/list") - public ApiResponse> getAllAttendanceByDateAndOrder( - @Parameter(description = "조회할 날짜 (YYYY-MM-DD)", required = true) + @GetMapping("/admin/attendance/list") + public List getAllAttendanceByDateAndOrder( + @Parameter(description = "조회할 날짜 (YYYY-MM-DD)", example = "2023-08-01") + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, + @Parameter(description = "조회할 차수", example = "1") + @RequestParam int order) { + return attendanceService.findAllByDateAndOrder(date, order); + } + + // 특정 사용자의 특정 날짜와 차수 출석 기록 조회 + @Operation(summary = "특정 사용자의 특정 날짜와 차수 출석 조회", description = "특정 사용자의 특정 날짜와 차수 출석 기록을 조회합니다.") + @ApiResponses(value = { + @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 = "출석 기록을 찾을 수 없음") + }) + @GetMapping("/admin/users/{userId}/attendance") + public UserAttendanceStatusRes getUserAttendanceByDateAndOrder( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "조회할 날짜 (YYYY-MM-DD)", example = "2023-08-01") @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, - @Parameter(description = "조회할 차수", required = true) + @Parameter(description = "조회할 차수", example = "1") @RequestParam int order) { + return attendanceService.findByUserIdAndDateAndOrder(userId, date, order); + } + + // 특정 출석 ID로 출석 기록 조회 + @Operation(summary = "특정 출석 기록 조회", description = "특정 학생의 특정 출석 기록을 ID로 조회합니다.") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "출석 기록을 찾을 수 없음") + }) + @GetMapping("/admin/users/{userId}/attendance/{attendanceId}") + public UserAttendanceStatusRes getAttendanceById( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId, + @Parameter(description = "출석 ID", example = "1") + @PathVariable Long attendanceId) { - List attendances = attendanceService.findAllByDateAndOrder(date, order); - return ApiResponse.success(attendances); + UserAttendanceStatusRes attendance = attendanceService.findById(attendanceId); + + if (attendance == null) { + throw new RuntimeException("출석 기록을 찾을 수 없습니다"); + } + + // 요청된 userId와 조회된 출석 기록의 userId가 일치하는지 확인 + if (!attendance.getUserId().equals(userId)) { + throw new RuntimeException("요청된 사용자 ID와 출석 기록의 사용자 ID가 일치하지 않습니다"); + } + + return attendance; + } + + // 학생용 출석 현황 조회 + @Operation(summary = "학생별 출석 현황 조회", description = "특정 학생의 출석 현황을 조회합니다.") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청") + }) + @GetMapping("/attendance/{userId}") + public List getUserAttendances( + @Parameter(description = "사용자 ID", example = "1") + @PathVariable Long userId) { + return attendanceService.findAllByUserId(userId); } } \ No newline at end of file diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java index 2c6973c..0bea7c5 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/request/UpdateAttendanceStatusReq.java @@ -12,9 +12,6 @@ @AllArgsConstructor @Schema(description = "출석 상태 수정 요청") public class UpdateAttendanceStatusReq { - @Schema(description = "출석 기록 ID", example = "1") - private Long attendanceId; - @Schema(description = "변경할 출석 상태", example = "true") private boolean status; } \ No newline at end of file diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java index 4a1adb8..ae0dec4 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/service/AttendanceService.java @@ -247,4 +247,82 @@ public List findAllByDateAndOrder(LocalDate date, int o .sorted(Comparator.comparing(UserAttendanceStatusRes::getUsername)) .toList(); } + + // 특정 학생의 모든 출석 현황 조회 + public List findAllByUserId(Long userId) { + // 해당 사용자의 모든 출석 기록 조회 + List attendances = attendanceRepository.findByUserId(userId); + + // 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()) + .build(); + }) + .sorted(Comparator.comparing(UserAttendanceStatusRes::getDate).reversed() + .thenComparing(UserAttendanceStatusRes::getOrder)) + .toList(); + } + + // 특정 사용자의 특정 출석 기록 삭제 + @Transactional + public boolean deleteAttendance(Long attendanceId) { + Optional attendanceOpt = attendanceRepository.findById(attendanceId); + + if (attendanceOpt.isEmpty()) { + return false; + } + + attendanceRepository.delete(attendanceOpt.get()); + return true; + } + + // 특정 사용자의 특정 날짜와 차수 출석 기록 조회 + public UserAttendanceStatusRes findByUserIdAndDateAndOrder(Long userId, LocalDate date, int order) { + Optional attendanceOpt = attendanceRepository.findByUserIdAndDateAndOrder(userId, date, order); + + if (attendanceOpt.isEmpty()) { + return null; + } + + Attendance attendance = attendanceOpt.get(); + 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()) + .build(); + } + + // 특정 출석 ID로 출석 기록 조회 + public UserAttendanceStatusRes findById(Long attendanceId) { + Optional attendanceOpt = attendanceRepository.findById(attendanceId); + + if (attendanceOpt.isEmpty()) { + return null; + } + + Attendance attendance = attendanceOpt.get(); + 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()) + .build(); + } } From bb1170db2f143309948e5c318ef5162e6d494136 Mon Sep 17 00:00:00 2001 From: NamKyeongMin Date: Fri, 23 May 2025 15:09:57 +0900 Subject: [PATCH 64/66] =?UTF-8?q?[fix]:=20=EC=88=98=EA=B0=95=EC=83=9D=20?= =?UTF-8?q?=EB=94=94=ED=85=8C=EC=9D=BC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20->?= =?UTF-8?q?=20=EA=B3=BC=EC=A0=9C=ED=99=95=EC=9D=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20api=20=EB=84=98=EA=B9=80=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/admin/AdminStudentAssignment.jsx | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/frontend/src/pages/admin/AdminStudentAssignment.jsx b/frontend/src/pages/admin/AdminStudentAssignment.jsx index 9424ca7..edc9d88 100644 --- a/frontend/src/pages/admin/AdminStudentAssignment.jsx +++ b/frontend/src/pages/admin/AdminStudentAssignment.jsx @@ -5,7 +5,10 @@ import WeeklyOpenBlock from "../../components/WeeklyOpenBlock"; import AssignmentInfoBlock from "../../components/AssignmentInfoBlock"; import api from "../../api/api"; import styles from "./AdminStudentAssignment.module.css"; -import { submitAssignmentStatus, updateAssignmentStatus } from "../../api/assignment"; +import { + submitAssignmentStatus, + updateAssignmentStatus, +} from "../../api/assignment"; const AdminStudentAssignment = () => { const { studentId, week } = useParams(); @@ -15,7 +18,8 @@ const AdminStudentAssignment = () => { const [selectedWeekLabel, setSelectedWeekLabel] = useState(null); useEffect(() => { - api.get(`/admin/users/${userId}`).then((res) => { + // 기존 (오류 발생) api.get(`/admin/users/${userId}`).then((res) => { + api.get(`/admin/users/${studentId}`).then((res) => { setStudentInfo(res.data.data); }); @@ -63,7 +67,7 @@ const AdminStudentAssignment = () => { task.modified = true; setWeeks(updated); }; -/* + /* const handleSave = async (taskId, status) => { await api.put("/admin/assignment/status", { assignmentId: taskId, @@ -71,30 +75,32 @@ const AdminStudentAssignment = () => { }); }; */ -const handleSave = async (taskId, status) => { - const userId = parseInt(studentId); // 문자열일 수 있으니 숫자로 변환 + const handleSave = async (taskId, status) => { + const userId = parseInt(studentId); // 문자열일 수 있으니 숫자로 변환 - try { - // PUT 요청 시도 (기존 과제 수정) - await updateAssignmentStatus(userId, taskId, status); - alert("과제 상태가 수정되었습니다."); - } catch (err) { - console.warn("PUT 실패, POST 시도"); try { - // 없으면 POST 요청 (새 과제 등록) - await submitAssignmentStatus(userId, taskId, status); - alert("과제 상태가 등록되었습니다."); + // PUT 요청 시도 (기존 과제 수정) + await updateAssignmentStatus(userId, taskId, status); + alert("과제 상태가 수정되었습니다."); } catch (err) { - alert("상태 저장 실패"); - console.error(err); + console.warn("PUT 실패, POST 시도"); + try { + // 없으면 POST 요청 (새 과제 등록) + await submitAssignmentStatus(userId, taskId, status); + alert("과제 상태가 등록되었습니다."); + } catch (err) { + alert("상태 저장 실패"); + console.error(err); + } } - } -}; + }; return (
window.history.back()} /> @@ -110,14 +116,23 @@ const handleSave = async (taskId, status) => {

{weekItem.label}

{weekItem.days.map((dayItem, dayIdx) => (
-

{dayItem.day}   {dayItem.subject}

+

+ {dayItem.day}   {dayItem.subject} +

{dayItem.tasks.map((task, taskIdx) => (
{task.label}