diff --git a/frontend/src/App.js b/frontend/src/App.js
index 13c568a..0e5c892 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -10,6 +10,8 @@ import PiroCheckMain from './pages/pirocheck/PIroCheckMain';
import Attendance from './pages/pirocheck/attendance/Attendance'
import Assignment from './pages/pirocheck/assignment/Assignment';
import Deposit from './pages/pirocheck/deposit/Deposit';
+import StudentList from './pages/pirocheck/students/StudentList';
+import StudentDetail from './pages/pirocheck/students/StudentDetail';
function App() {
return (
@@ -35,6 +37,8 @@ function App() {
}/>
}/>
}/>
+ }/>
+ }/>
diff --git a/frontend/src/assets/images/icon_arrow_right.svg b/frontend/src/assets/images/icon_arrow_right.svg
new file mode 100644
index 0000000..c5aa6f6
--- /dev/null
+++ b/frontend/src/assets/images/icon_arrow_right.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/images/icon_togle1.svg b/frontend/src/assets/images/icon_togle1.svg
new file mode 100644
index 0000000..3d85458
--- /dev/null
+++ b/frontend/src/assets/images/icon_togle1.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/images/icon_togle2.svg b/frontend/src/assets/images/icon_togle2.svg
new file mode 100644
index 0000000..9aa266f
--- /dev/null
+++ b/frontend/src/assets/images/icon_togle2.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/images/logo2.svg b/frontend/src/assets/images/logo2.svg
new file mode 100644
index 0000000..d667deb
--- /dev/null
+++ b/frontend/src/assets/images/logo2.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/src/assets/images/profile.svg b/frontend/src/assets/images/profile.svg
new file mode 100644
index 0000000..84191cb
--- /dev/null
+++ b/frontend/src/assets/images/profile.svg
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/pages/pirocheck/students/StudentDetail.js b/frontend/src/pages/pirocheck/students/StudentDetail.js
new file mode 100644
index 0000000..6c31628
--- /dev/null
+++ b/frontend/src/pages/pirocheck/students/StudentDetail.js
@@ -0,0 +1,294 @@
+import { useState, useEffect } from 'react';
+import { useParams, useLocation } from 'react-router-dom';
+import styles from './StudentDetail.module.css';
+import ProfileImg from '../../../assets/images/profile.svg';
+import Logo2 from '../../../assets/images/logo2.svg';
+import Toggle1 from '../../../assets/images/icon_togle1.svg';
+import Toggle2 from '../../../assets/images/icon_togle2.svg';
+
+const IS_MOCK = true;
+
+const MOCK_DETAIL = {
+ deposit: { amount: 100000, ascentDefence: 10000 },
+ weeks: [
+ {
+ week: 1,
+ days: [
+ {
+ day: 'TUESDAY',
+ sessionDate: '2026-06-24',
+ sessionTitles: 'HTML/CSS 기초, Git 기초',
+ attendances: [
+ { attendanceId: 1, attendanceOrder: '1차', attended: true },
+ { attendanceId: 2, attendanceOrder: '2차', attended: true },
+ { attendanceId: 3, attendanceOrder: '3차', attended: true },
+ ],
+ assignments: [
+ { assignmentItemId: 1, title: '코딩앵무 클론 코딩', submitted: 'SUBMITTED' },
+ { assignmentItemId: 2, title: '피로그래밍 페이지 클론 코딩', submitted: 'SUBMITTED' },
+ ],
+ },
+ {
+ day: 'THURSDAY',
+ sessionDate: '2026-06-26',
+ sessionTitles: 'JS 기초, JS 심화',
+ attendances: [
+ { attendanceId: 4, attendanceOrder: '1차', attended: false },
+ { attendanceId: 5, attendanceOrder: '2차', attended: false },
+ { attendanceId: 6, attendanceOrder: '3차', attended: false },
+ ],
+ assignments: [
+ { assignmentItemId: 3, title: '코딩앵무 클론 코딩', submitted: 'NOT_SUBMITTED' },
+ ],
+ },
+ {
+ day: 'SATURDAY',
+ sessionDate: '2026-06-28',
+ sessionTitles: 'DB 개론',
+ attendances: [
+ { attendanceId: 7, attendanceOrder: '1차', attended: false },
+ { attendanceId: 8, attendanceOrder: '2차', attended: false },
+ { attendanceId: 9, attendanceOrder: '3차', attended: false },
+ ],
+ assignments: [],
+ },
+ ],
+ },
+ { week: 2, days: [] },
+ { week: 3, days: [] },
+ { week: 4, days: [] },
+ { week: 5, days: [] },
+ ],
+};
+
+const dayLabel = { TUESDAY: 'TUE', THURSDAY: 'THU', SATURDAY: 'SAT' };
+const statusOptions = ['SUBMITTED', 'LATE', 'NOT_SUBMITTED'];
+const statusLabel = { SUBMITTED: '성공', LATE: '미달', NOT_SUBMITTED: '실패' };
+
+// 커리큘럼 데이터에서 날짜별 세션 제목 추출
+function extractSessionTitles(curriculums, sessionDate) {
+ const day = curriculums.find(c => c.sessionDate === sessionDate);
+ if (!day || !day.sessions) return '';
+ return day.sessions.map(s => s.title).join(', ');
+}
+
+function WeekBlock({ weekData, onChange }) {
+ const [isOpen, setIsOpen] = useState(false);
+ const [openDays, setOpenDays] = useState({});
+
+ const toggleDay = (day) => {
+ setOpenDays(prev => ({ ...prev, [day]: !prev[day] }));
+ };
+
+ return (
+
+
setIsOpen(p => !p)}>
+
+

+
WEEK {weekData.week}
+
+

+
+
+ {isOpen && (
+
+ {weekData.days.length === 0 && (
+
데이터가 없습니다.
+ )}
+ {weekData.days.map((day, i) => (
+
+
toggleDay(day.day)}>
+
+ {dayLabel[day.day]}
+ {day.sessionTitles || day.sessionDate}
+
+

+
+
+ {openDays[day.day] && (
+
+ {/* 출석 */}
+
+
출석
+
+ {day.attendances.map((att, j) => (
+
+ {att.attendanceOrder}
+
+
+ ))}
+
+
+
+ {/* 과제 */}
+ {day.assignments.length > 0 && (
+
+
과제
+
+ {day.assignments.map((asg, j) => (
+
+ {asg.title}
+
+
+ ))}
+
+
+ )}
+
+
+
+ )}
+
+ {i < weekData.days.length - 1 &&
}
+
+ ))}
+
+ )}
+
+ );
+}
+
+function StudentDetail() {
+ const { userId } = useParams();
+ const location = useLocation();
+ const studentName = location.state?.name || '수강생';
+
+ const [data, setData] = useState(null);
+ const [defence, setDefence] = useState('');
+
+ useEffect(() => {
+ if (IS_MOCK) {
+ setData(MOCK_DETAIL);
+ setDefence(MOCK_DETAIL.deposit.ascentDefence.toString());
+ return;
+ }
+
+ const fetchData = async () => {
+ // TODO: GET /api/admin/{userId}/deposit/view
+ // TODO: GET /api/admin/admin/student/{userId}/status/{week} (1~5주차)
+
+ // 커리큘럼에서 세션 제목 가져오기
+ const curriculumRes = await fetch('/api/curriculums');
+ const curriculums = await curriculumRes.json();
+
+ // weeks 데이터에 sessionTitles 추가
+ // const mergedWeeks = weeks.map(w => ({
+ // ...w,
+ // days: w.days.map(d => ({
+ // ...d,
+ // sessionTitles: extractSessionTitles(curriculums, d.sessionDate),
+ // }))
+ // }));
+ };
+ fetchData();
+ }, [userId]);
+
+ const handleSaveDefence = async () => {
+ if (IS_MOCK) { alert('저장됨 (임시)'); return; }
+ // TODO: PUT /api/admin/{userId}/deposit-defend
+ };
+
+ const handleStatusChange = (type, week, day, id, value) => {
+ setData(prev => {
+ const newWeeks = prev.weeks.map(w => {
+ if (w.week !== week) return w;
+ return {
+ ...w,
+ days: w.days.map(d => {
+ if (d.day !== day) return d;
+ if (type === 'attendance') {
+ return {
+ ...d,
+ attendances: d.attendances.map(a =>
+ a.attendanceId === id ? { ...a, attended: value } : a
+ ),
+ };
+ } else {
+ return {
+ ...d,
+ assignments: d.assignments.map(a =>
+ a.assignmentItemId === id ? { ...a, submitted: value } : a
+ ),
+ };
+ }
+ }),
+ };
+ });
+ return { ...prev, weeks: newWeeks };
+ });
+ };
+
+ const handleSaveAll = async () => {
+ if (IS_MOCK) { alert('전체 저장됨 (임시)'); return; }
+ // TODO: PATCH /api/admin/users/{userId}/weeks/{week} 주차별로 호출
+ };
+
+ if (!data) return null;
+
+ return (
+
+ {IS_MOCK && (
+
+ ⚠️ 현재 임시 데이터로 표시 중입니다.
+
+ )}
+
+
+
+

+
{studentName}
+
+
+
+
+
잔여 보증금
+
{data.deposit.amount.toLocaleString()}원
+
+
+
보증금 방어권
+
+ setDefence(e.target.value)}
+ />
+ 원
+
+
+
+
+
+ {data.weeks.map((w, i) => (
+
+ ))}
+
+
+
+
+ );
+}
+
+export default StudentDetail;
\ No newline at end of file
diff --git a/frontend/src/pages/pirocheck/students/StudentDetail.module.css b/frontend/src/pages/pirocheck/students/StudentDetail.module.css
new file mode 100644
index 0000000..e073df1
--- /dev/null
+++ b/frontend/src/pages/pirocheck/students/StudentDetail.module.css
@@ -0,0 +1,343 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 60px 20px;
+ min-height: calc(100vh - 100px);
+ background: var(--black);
+}
+
+.mockBanner {
+ background: #5a3e00;
+ color: #ffd166;
+ font-family: var(--font-main);
+ font-size: 0.85rem;
+ padding: 10px 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ width: 480px;
+ box-sizing: border-box;
+ text-align: center;
+}
+
+.card {
+ width: 480px;
+ background: var(--gray600);
+ border-radius: 20px;
+ padding: 40px 40px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+/* 프로필 */
+.profileArea {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+}
+
+.profileImg {
+ width: 100px;
+ height: 100px;
+ margin-left: 2px;
+ object-fit: cover;
+}
+
+.profileName {
+ font-family: var(--font-main);
+ font-size: 1.8rem;
+ font-weight: 700;
+ color: var(--dark);
+}
+
+/* 보증금 */
+.depositRow {
+ display: flex;
+ gap: 12px;
+}
+
+.depositBoxGreen {
+ flex: 1;
+ background: var(--light);
+ border-radius: 10px;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.depositBoxGray {
+ flex: 1;
+ background: var(--pale);
+ border-radius: 10px;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.depositLabel {
+ font-family: var(--font-main);
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: var(--gray600);
+}
+
+.depositValue {
+ font-family: var(--font-main);
+ font-size: 1.2rem;
+ font-weight: 700;
+ color: var(--black);
+}
+
+.depositEditRow {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.defenceInput {
+ width: 80px;
+ border: none;
+ background: transparent;
+ font-family: var(--font-main);
+ font-size: 1.2rem;
+ font-weight: 700;
+ color: var(--black);
+ outline: none;
+}
+
+.won {
+ font-family: var(--font-main);
+ font-size: 1.2rem;
+ font-weight: 700;
+ color: var(--black);
+}
+
+.saveBtn {
+ padding: 4px 10px;
+ background: var(--gray200);
+ border: none;
+ border-radius: 6px;
+ color: var(--gray600);
+ font-family: var(--font-main);
+ font-size: 0.75rem;
+ font-weight: 550;
+ cursor: pointer;
+ margin-left: 30px;
+}
+
+.saveBtn:hover { background: var(--gray600); color: var(--white); transition: all ease-in-out 0.2s; }
+
+/* 주차 블록 */
+.weekBlock {
+ background: var(--gray600);
+ border-radius: 12px;
+ overflow: hidden;
+}
+
+.weekHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 20px;
+ cursor: pointer;
+ color: var(--white);
+}
+
+.weekLeft {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.weekLogo {
+ width: 30px;
+ height: 30px;
+ object-fit: contain;
+}
+
+.weekLabel {
+ font-family: var(--font-main);
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: var(--white);
+ margin-left: 5px;
+}
+
+/* 토글 애니메이션 */
+.toggleIcon {
+ width: 15px;
+ height: 15px;
+ transition: transform 0.3s ease;
+}
+
+.toggleIcon2 {
+ width: 20px;
+ height: 20px;
+ transition: transform 0.3s ease;
+ vertical-align: middle;
+}
+
+.toggleOpen {
+ transform: rotate(180deg);
+}
+
+.weekBody {
+ padding: 0 0 16px;
+ margin-left: 45px;
+ margin-right: 20px;
+}
+
+.empty {
+ color: #aaa;
+ font-family: var(--font-main);
+ font-size: 1rem;
+ text-align: center;
+ padding: 12px 0;
+}
+
+/* 요일 블록 */
+
+.dayHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+ padding: 6px 0;
+}
+
+.dayLeft {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.dayLabel {
+ color: var(--dark);
+ font-family: var(--font-main);
+ font-size: 1.2rem;
+ font-weight: 650;
+}
+
+.sessionDate {
+ color: var(--light);
+ font-family: var(--font-main);
+ font-size: 1rem;
+ font-weight: 500;
+}
+
+.dayBody {
+ padding: 5px 16px 0 5px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+/* 출석/과제 그룹 - 레이블 옆에 아이템들 */
+.statusGroup {
+ display: flex;
+ align-items: flex-start;
+ gap: 16px;
+}
+
+.sectionLabel {
+ color: var(--white);
+ font-family: var(--font-main);
+ font-size: 1.1rem;
+ font-weight: 500;
+ min-width: 30px;
+ padding-top: 4px;
+}
+
+.statusItems {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.statusItem {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.itemLabel {
+ color: var(--white);
+ font-family: var(--font-main);
+ font-size: 1rem;
+ font-weight: 450;
+ padding-top: 3px;
+}
+
+.select {
+ padding: 3px 28px 3px 12px;
+ width: 80px;
+ box-sizing: border-box;
+
+ background: var(--gray600);
+ border: 1px solid var(--white);
+ border-radius: 6px;
+ color: var(--white);
+ font-family: var(--font-main);
+ font-size: 0.85rem;
+ cursor: pointer;
+
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+
+ background-image: url("data:image/svg+xml;utf8,");
+ background-repeat: no-repeat;
+
+ background-position: right 10px center;
+}
+
+.select::-ms-expand {
+ display: none;
+}
+
+.saveWeekBtn {
+ margin: 20px 0;
+ padding: 5px 30px;
+ background: transparent;
+ border: 1.5px solid var(--main);
+ border-radius: 10px;
+ color: var(--main);
+ font-family: var(--font-main);
+ font-size: 1rem;
+ font-weight: 600;
+ cursor: pointer;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.saveWeekBtn:hover { background: var(--main); color: var(--white); transition: all ease-in-out 0.2s; }
+
+.divider {
+ border: none;
+ border-top: 1px solid #444;
+ margin: 10px 0;
+}
+
+/* 전체 저장 */
+.saveAllBtn {
+ width: 60%;
+ margin: 0 auto;
+ padding: 10px 0;
+ background: var(--dark);
+ border: none;
+ border-radius: 10px;
+ color: var(--white);
+ font-family: var(--font-main);
+ font-size: 1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: opacity 0.2s;
+}
+
+.saveAllBtn:hover { background: var(--main); transition: all ease-in-out 0.2s; }
\ No newline at end of file
diff --git a/frontend/src/pages/pirocheck/students/StudentList.js b/frontend/src/pages/pirocheck/students/StudentList.js
new file mode 100644
index 0000000..98790c8
--- /dev/null
+++ b/frontend/src/pages/pirocheck/students/StudentList.js
@@ -0,0 +1,79 @@
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import styles from './StudentList.module.css';
+import ArrowRight from '../../../assets/images/icon_arrow_right.svg';
+
+const MOCK_STUDENTS = [
+ { userId: 1, name: '김피로' },
+ { userId: 2, name: '이피로' },
+ { userId: 3, name: '박피로' },
+ { userId: 4, name: '최피로' },
+ { userId: 5, name: '정피로' },
+];
+
+const IS_MOCK = true;
+
+function StudentList() {
+ const navigate = useNavigate();
+ const [students, setStudents] = useState([]);
+ const [search, setSearch] = useState('');
+
+ const fetchStudents = async (keyword = '') => {
+ if (IS_MOCK) {
+ if (keyword) {
+ setStudents(MOCK_STUDENTS.filter(s => s.name.includes(keyword)));
+ } else {
+ setStudents(MOCK_STUDENTS);
+ }
+ return;
+ }
+ const url = keyword
+ ? `/api/admin/studentlist/search?name=${keyword}`
+ : '/api/admin/studentlist';
+ const res = await fetch(url);
+ const data = await res.json();
+ setStudents(data);
+ };
+
+ useEffect(() => { fetchStudents(); }, []);
+
+ const handleSearch = () => fetchStudents(search);
+
+ return (
+
+ {IS_MOCK && (
+
+ ⚠️ 현재 임시 데이터로 표시 중입니다.
+
+ )}
+
+
PIROGRAMMER
+
+
+ setSearch(e.target.value)}
+ onKeyDown={e => e.key === 'Enter' && handleSearch()}
+ />
+
+
+
+
+ {students.map((s, i) => (
+
+ ))}
+
+
+ );
+}
+
+export default StudentList;
\ No newline at end of file
diff --git a/frontend/src/pages/pirocheck/students/StudentList.module.css b/frontend/src/pages/pirocheck/students/StudentList.module.css
new file mode 100644
index 0000000..4eea6e6
--- /dev/null
+++ b/frontend/src/pages/pirocheck/students/StudentList.module.css
@@ -0,0 +1,103 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 60px 20px;
+ min-height: calc(100vh - 100px);
+ background: var(--black);
+}
+
+.mockBanner {
+ background: #5a3e00;
+ color: #ffd166;
+ font-family: var(--font-main);
+ font-size: 0.85rem;
+ padding: 10px 20px;
+ border-radius: 8px;
+ margin-bottom: 20px;
+ width: 480px;
+ box-sizing: border-box;
+ text-align: center;
+}
+
+.title {
+ font-family: var(--font-title);
+ font-size: 3rem;
+ font-weight: 800;
+ color: var(--main);
+ margin-bottom: 36px;
+ letter-spacing: 0;
+}
+
+.searchRow {
+ display: flex;
+ align-items: center;
+ background: var(--gray600);
+ border-radius: 10px;
+ overflow: hidden;
+ width: 480px;
+ margin-bottom: 30px;
+ padding: 6px 6px 6px 20px;
+}
+
+.searchInput {
+ flex: 1;
+ background: transparent;
+ border: none;
+ outline: none;
+ font-family: var(--font-main);
+ font-size: 1rem;
+ color: var(--white);
+}
+
+.searchInput::placeholder { color: var(--gray200); }
+
+.searchBtn {
+ padding: 7px 15px;
+ background: var(--dark);
+ border: none;
+ border-radius: 10px;
+ color: var(--white);
+ font-family: var(--font-main);
+ font-size: 0.95rem;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.searchBtn:hover {
+ background: var(--main);
+ transition: all ease-in-out 0.2s;
+}
+
+.list {
+ width: 450px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.studentItem {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: var(--gray600);
+ border: 1px solid var(--gray50);
+ border-radius: 10px;
+ padding: 7px 15px 7px 20px;
+ cursor: pointer;
+ transition: background 0.2s;
+ width: 100%;
+}
+
+.studentItem:hover { background: #4a4a4a; }
+
+.studentName {
+ font-family: var(--font-main);
+ font-size: 1rem;
+ font-weight: 500;
+ color: var(--white);
+}
+
+.arrow {
+ width: 25px; height: 25px;
+}
\ No newline at end of file