diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceMarkResponse.java b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceMarkResponse.java
new file mode 100644
index 0000000..fc38565
--- /dev/null
+++ b/backend/pirocheck/src/main/java/backend/pirocheck/Attendance/dto/response/AttendanceMarkResponse.java
@@ -0,0 +1,63 @@
+package backend.pirocheck.Attendance.dto.response;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AttendanceMarkResponse {
+ private String statusCode; // SUCCESS, INVALID_CODE, CODE_EXPIRED, ALREADY_MARKED, NO_ACTIVE_SESSION, ERROR
+ private String message;
+
+ // 성공 응답
+ public static AttendanceMarkResponse success() {
+ return AttendanceMarkResponse.builder()
+ .statusCode("SUCCESS")
+ .message("출석이 완료되었습니다")
+ .build();
+ }
+
+ // 유효하지 않은 코드
+ public static AttendanceMarkResponse invalidCode() {
+ return AttendanceMarkResponse.builder()
+ .statusCode("INVALID_CODE")
+ .message("유효하지 않은 출석 코드입니다")
+ .build();
+ }
+
+ // 만료된 코드
+ public static AttendanceMarkResponse codeExpired() {
+ return AttendanceMarkResponse.builder()
+ .statusCode("CODE_EXPIRED")
+ .message("만료된 출석 코드입니다")
+ .build();
+ }
+
+ // 이미 출석 완료
+ public static AttendanceMarkResponse alreadyMarked() {
+ return AttendanceMarkResponse.builder()
+ .statusCode("ALREADY_MARKED")
+ .message("이미 출석 처리되었습니다")
+ .build();
+ }
+
+ // 활성화된 출석 세션 없음
+ public static AttendanceMarkResponse noActiveSession() {
+ return AttendanceMarkResponse.builder()
+ .statusCode("NO_ACTIVE_SESSION")
+ .message("현재 활성화된 출석 세션이 없습니다")
+ .build();
+ }
+
+ // 일반 에러
+ public static AttendanceMarkResponse error(String message) {
+ return AttendanceMarkResponse.builder()
+ .statusCode("ERROR")
+ .message(message)
+ .build();
+ }
+}
\ No newline at end of file
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 1dcff3e..d149b66 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,7 @@ public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 백엔드 API 요청에만 CORS 허용
- .allowedOrigins("http://www.pirocheck.org") // 프론트 배포 URL
+ .allowedOrigins("http://localhost:5173", "https://www.pirocheck.org") // 프론트 배포 URL
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메서드
.allowedHeaders("*")
.allowCredentials(true); // 세션 쿠키 주고받기 허용
diff --git a/frontend/public/assets/img/managestudent.svg b/frontend/public/assets/img/managestudent.svg
new file mode 100644
index 0000000..bad007f
--- /dev/null
+++ b/frontend/public/assets/img/managestudent.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index b11b9d0..35cf6b5 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -6,10 +6,11 @@ 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";
+import AdminStudentAttendance from "./pages/admin/AdminStudentAttendance";
function App() {
return (
@@ -22,9 +23,10 @@ function App() {
{date} 출석 수정
+ +{week}주차
+수강생 관리
출석코드 생성
; + const [code, setCode] = useState(""); + + // 출석코드 생성 + const generateCode = async () => { + try { + const res = await api.post("/attendance/start"); + const newCode = res.data.data.code; + setCode(newCode); + } catch (error) { + alert( + "출석코드 생성 실패: " + (error.response?.data?.message || "서버 오류") + ); + } + }; + + // 출석코드 만료 + const expireCode = async () => { + try { + const res = await api.put("/attendance/expire-latest"); + alert(res.data.message || "출석코드가 만료되었습니다"); + setCode(""); + } catch (error) { + alert( + "출석코드 만료 실패: " + (error.response?.data?.message || "서버 오류") + ); + } + }; + + return ( +수강생 관리
; +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"; + +const ManageStudent = () => { + const [studentName, setStudentName] = useState([""]); + const [page, setPage] = useState(1); + const [students, setStudents] = useState([]); // 서버 데이터 저장 + + const studentsPerPage = 6; + + 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 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 ( +| npm | +yarn | +
|---|---|
| + +```sh +npm install @inquirer/prompts +``` + + | ++ +```sh +yarn add @inquirer/prompts +``` + + | +
| Or | +|
| + +```sh +npm install @inquirer/confirm +``` + + | ++ +```sh +yarn add @inquirer/confirm +``` + + | +
| npm | +yarn | +
|---|---|
| + +```sh +npm install @inquirer/core +``` + + | ++ +```sh +yarn add @inquirer/core +``` + + | +