From 6cbcdcd06c330ffde71724215fc218d842a32ed8 Mon Sep 17 00:00:00 2001 From: plumbestie Date: Sun, 24 May 2026 11:59:59 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[Feat]=20Deposit,=20Studentlist,=20Assignme?= =?UTF-8?q?nt=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/pirocheck/assignment/Assignment.js | 31 +--------------- .../src/pages/pirocheck/deposit/Deposit.js | 22 +---------- .../pages/pirocheck/students/StudentList.js | 37 +++++-------------- 3 files changed, 12 insertions(+), 78 deletions(-) diff --git a/frontend/src/pages/pirocheck/assignment/Assignment.js b/frontend/src/pages/pirocheck/assignment/Assignment.js index 9f1644a..c081f07 100644 --- a/frontend/src/pages/pirocheck/assignment/Assignment.js +++ b/frontend/src/pages/pirocheck/assignment/Assignment.js @@ -14,26 +14,7 @@ const dayMap = { SATURDAY: 'SAT', }; -// 임시 데이터 (백엔드 연동 전) -const MOCK_DATA = [ - { - week: '1', - assignments: [ - { assignmentId: 1, title: '코딩앵무 클론 코딩', week: '1', day: 'TUESDAY', sessionDate: 'HTML/CSS, Git 기초', submitted: 'SUBMITTED' }, - { assignmentId: 2, title: '피로그래밍 페이지 클론 코딩', week: '1', day: 'TUESDAY', sessionDate: 'HTML/CSS, Git 기초', submitted: 'NOT_SUBMITTED' }, - { assignmentId: 3, title: '코딩앵무 클론 코딩', week: '1', day: 'THURSDAY', sessionDate: 'JS', submitted: 'SUBMITTED' }, - { assignmentId: 4, title: '숫자야구 게임', week: '1', day: 'THURSDAY', sessionDate: 'JS', submitted: 'LATE' }, - { assignmentId: 5, title: '파이썬 코딩도장', week: '1', day: 'THURSDAY', sessionDate: 'JS', submitted: 'NOT_SUBMITTED' }, - { assignmentId: 6, title: '아르사 팀플', week: '1', day: 'SATURDAY', sessionDate: 'DB 개론', submitted: 'SUBMITTED' }, - ], - }, - { week: '2', assignments: [] }, - { week: '3', assignments: [] }, - { week: '4', assignments: [] }, - { week: '5', assignments: [] }, -]; - -const IS_MOCK = true; // 백엔드 연동 시 false로 변경 +const IS_MOCK = false; // 제출 상태 아이콘 (부원용) function StatusIcon({ status }) { @@ -164,10 +145,6 @@ function Assignment() { const [modalItem, setModalItem] = useState(undefined); // undefined=닫힘, null=생성, object=수정 const fetchAll = async () => { - if (IS_MOCK) { - setWeeks(MOCK_DATA); - return; - } const results = await Promise.all( ['1', '2', '3', '4', '5'].map(w => fetch(`/api/assignments/me/${w}`) @@ -182,17 +159,11 @@ function Assignment() { const handleDelete = async (assignmentId) => { if (!window.confirm('삭제하시겠습니까?')) return; - if (!IS_MOCK) { - await fetch(`/api/assignments/${assignmentId}`, { method: 'DELETE' }); - } fetchAll(); }; return (
- {IS_MOCK && ( -
⚠️ 현재 임시 데이터로 표시 중입니다. 백엔드 연동 후 IS_MOCK을 false로 변경하세요.
- )}
ASSIGNMENT CHECK
{weeks.map((w, i) => ( diff --git a/frontend/src/pages/pirocheck/deposit/Deposit.js b/frontend/src/pages/pirocheck/deposit/Deposit.js index 8b7f2f9..6e2aab4 100644 --- a/frontend/src/pages/pirocheck/deposit/Deposit.js +++ b/frontend/src/pages/pirocheck/deposit/Deposit.js @@ -1,40 +1,22 @@ import { useState, useEffect } from 'react'; import styles from './Deposit.module.css'; -// 임시 데이터 -const MOCK_DATA = { - amount: 100000, - descentAssignment: 10000, - descentAttendance: 10000, - ascentDefence: 10000, -}; - -const IS_MOCK = true; // 백엔드 연동 시 false로 변경 +const IS_MOCK = false function Deposit() { const [deposit, setDeposit] = useState(null); useEffect(() => { - if (IS_MOCK) { - setDeposit(MOCK_DATA); - return; - } fetch('/api/deposit/me') .then(r => r.json()) .then(data => setDeposit(data)) - .catch(() => setDeposit(MOCK_DATA)); + .catch(() => {}); }, []); if (!deposit) return null; return (
- {IS_MOCK && ( -
- ⚠️ 현재 임시 데이터로 표시 중입니다. 백엔드 연동 후 IS_MOCK을 false로 변경하세요. -
- )} -
DEPOSIT CHECK
diff --git a/frontend/src/pages/pirocheck/students/StudentList.js b/frontend/src/pages/pirocheck/students/StudentList.js index 98790c8..64d971b 100644 --- a/frontend/src/pages/pirocheck/students/StudentList.js +++ b/frontend/src/pages/pirocheck/students/StudentList.js @@ -3,15 +3,7 @@ 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; +const IS_MOCK = false; function StudentList() { const navigate = useNavigate(); @@ -19,20 +11,14 @@ function StudentList() { 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); + try { + const url = keyword + ? `/api/admin/studentlist/search?name=${keyword}` + : '/api/admin/studentlist'; + const res = await fetch(url); + const data = await res.json(); + setStudents(Array.isArray(data) ? data : data.data || []); + } catch (e) {} }; useEffect(() => { fetchStudents(); }, []); @@ -41,11 +27,6 @@ function StudentList() { return (
- {IS_MOCK && ( -
- ⚠️ 현재 임시 데이터로 표시 중입니다. -
- )}
PIROGRAMMER
From 76359168fdc1c1ad64e43c28b187ed48309acd16 Mon Sep 17 00:00:00 2001 From: plumbestie Date: Sun, 24 May 2026 12:22:39 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[Feat]=20Attendance=20API=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=EB=B0=8F=20API=20=EC=9A=94=EC=B2=AD=20=EC=BD=94?= =?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 --- frontend/package.json | 2 +- .../src/pages/pirocheck/attendance/Attendance.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 56b3680..142f4f5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,6 @@ { "name": "piroinfront", + "proxy": "http://localhost:8080", "version": "0.1.0", "private": true, "dependencies": { @@ -16,7 +17,6 @@ "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, - "proxy": "http://13.209.73.127:8080", "scripts": { "start": "react-scripts start", "build": "react-scripts build", diff --git a/frontend/src/pages/pirocheck/attendance/Attendance.js b/frontend/src/pages/pirocheck/attendance/Attendance.js index 64d25e2..734a51c 100644 --- a/frontend/src/pages/pirocheck/attendance/Attendance.js +++ b/frontend/src/pages/pirocheck/attendance/Attendance.js @@ -14,8 +14,11 @@ function cloverForSlot(status) { return 미정; } -function slotIcon(status) { - if (status === true) return 출석; +function historyIcon(slots) { + const successCount = slots.filter(s => s.status === true).length; + if (successCount === 3) return 3회 출석; + if (successCount === 2) return 2회 출석; + if (successCount === 1) return 1회 출석; return 결석; } @@ -166,10 +169,7 @@ function MemberView() {
{row.week}주차
- {[0, 1, 2].map(j => { - const slot = row.slots[j] ?? { status: false }; - return
{slotIcon(slot.status)}
; - })} + {historyIcon(row.slots)}
))} From bd9b61ca4b80bd1c88daa351ef6203438d20f85f Mon Sep 17 00:00:00 2001 From: plumbestie Date: Sun, 24 May 2026 12:43:09 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[Feat]=20=ED=94=BC=EB=A1=9C=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20JWT=20=ED=86=A0=ED=81=B0=20=EA=B8=B0=EB=B0=98=20API?= =?UTF-8?q?=20=EC=9D=B8=EC=A6=9D=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/OnboardingPage.js | 13 +++++++++---- frontend/src/pages/login/LoginPage.js | 5 +++-- frontend/src/pages/pirocheck/PIroCheckMain.js | 1 - .../src/pages/pirocheck/assignment/Assignment.js | 5 +++-- .../src/pages/pirocheck/attendance/Attendance.js | 15 ++++++++------- frontend/src/pages/pirocheck/deposit/Deposit.js | 3 ++- .../src/pages/pirocheck/students/StudentDetail.js | 3 ++- .../src/pages/pirocheck/students/StudentList.js | 3 ++- frontend/src/utils/Api.js | 11 +++++++++++ 9 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 frontend/src/utils/Api.js diff --git a/frontend/src/pages/OnboardingPage.js b/frontend/src/pages/OnboardingPage.js index b819e21..933057d 100644 --- a/frontend/src/pages/OnboardingPage.js +++ b/frontend/src/pages/OnboardingPage.js @@ -7,10 +7,15 @@ function OnboardingPage() { const navigate = useNavigate(); useEffect(() => { - const timer = setTimeout(() => { - navigate('/login'); - }, 2000); - return () => clearTimeout(timer); + const timer = setTimeout(() => { + const token = localStorage.getItem('token'); + if (token) { + navigate('/pirocheck'); + } else { + navigate('/login'); + } + }, 2000); + return () => clearTimeout(timer); }, []); return ( diff --git a/frontend/src/pages/login/LoginPage.js b/frontend/src/pages/login/LoginPage.js index a232d89..bac5e88 100644 --- a/frontend/src/pages/login/LoginPage.js +++ b/frontend/src/pages/login/LoginPage.js @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; // 추가 +import { useNavigate } from 'react-router-dom'; +import { authFetch } from '../../utils/Api'; import styles from './LoginPage.module.css'; function LoginPage() { @@ -15,7 +16,7 @@ function LoginPage() { const handleLogin = async () => { try { - const response = await fetch('/api/auth/login', { + const response = await authFetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(form), diff --git a/frontend/src/pages/pirocheck/PIroCheckMain.js b/frontend/src/pages/pirocheck/PIroCheckMain.js index 678c45a..8faf3a2 100644 --- a/frontend/src/pages/pirocheck/PIroCheckMain.js +++ b/frontend/src/pages/pirocheck/PIroCheckMain.js @@ -1,4 +1,3 @@ -import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import styles from './PIroCheckMain.module.css'; diff --git a/frontend/src/pages/pirocheck/assignment/Assignment.js b/frontend/src/pages/pirocheck/assignment/Assignment.js index c081f07..aa2aaf2 100644 --- a/frontend/src/pages/pirocheck/assignment/Assignment.js +++ b/frontend/src/pages/pirocheck/assignment/Assignment.js @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { authFetch } from '../../../utils/Api'; import styles from './Assignment.module.css'; import LogoImg from '../../../assets/images/logo.png'; import EditIcon from '../../../assets/images/icon_edit.svg'; @@ -50,7 +51,7 @@ function AssignmentModal({ item, onClose, onSave }) { ? `/api/assignments/modify/${item.assignmentId}` : '/api/assignments/create'; const method = isEdit ? 'PATCH' : 'POST'; - await fetch(url, { + await authFetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: form.title, week: form.week, day: form.day }), @@ -147,7 +148,7 @@ function Assignment() { const fetchAll = async () => { const results = await Promise.all( ['1', '2', '3', '4', '5'].map(w => - fetch(`/api/assignments/me/${w}`) + authFetch(`/api/assignments/me/${w}`) .then(r => r.json()) .catch(() => ({ week: w, assignments: [] })) ) diff --git a/frontend/src/pages/pirocheck/attendance/Attendance.js b/frontend/src/pages/pirocheck/attendance/Attendance.js index 734a51c..fe8f350 100644 --- a/frontend/src/pages/pirocheck/attendance/Attendance.js +++ b/frontend/src/pages/pirocheck/attendance/Attendance.js @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { authFetch } from '../../../utils/Api'; import styles from './Attendance.module.css'; import CloverGreen from '../../../assets/images/CloverGreen.svg'; import CloverRed from '../../../assets/images/CloverRed.svg'; @@ -30,7 +31,7 @@ function AdminView() { useEffect(() => { const fetchActiveCode = async () => { try { - const res = await fetch('/api/admin/attendance/active-code'); + const res = await authFetch('/api/admin/attendance/active-code'); if (res.ok) { const data = await res.json(); if (!data.isExpired) { @@ -44,14 +45,14 @@ function AdminView() { }, []); const handleGenerate = async () => { - const res = await fetch('/api/admin/attendance/start', { method: 'POST' }); + const res = await authFetch('/api/admin/attendance/start', { method: 'POST' }); const data = await res.json(); setCode(data.code); setHasCode(true); }; const handleExpire = async () => { - await fetch('/api/admin/attendance/active-code/expire', { method: 'PUT' }); + await authFetch('/api/admin/attendance/active-code/expire', { method: 'PUT' }); setCode(null); setHasCode(false); }; @@ -75,7 +76,7 @@ function AdminView() { 종료 )} - 출석 관리 + 출석 관리
); @@ -99,7 +100,7 @@ function MemberView() { ] })); - fetch('/api/attendance/user') + authFetch('/api/attendance/user') .then(r => r.json()) .then(data => { const apiData = data.data || []; @@ -115,7 +116,7 @@ function MemberView() { const handleSubmit = async () => { if (!inputCode.trim()) return; - const res = await fetch('/api/attendance/mark', { + const res = await authFetch('/api/attendance/mark', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code: inputCode }), @@ -126,7 +127,7 @@ function MemberView() { if (result.statusCode === 'SUCCESS') { setMessage('출석 성공!'); const today = new Date().toISOString().split('T')[0]; - fetch(`/api/attendance/user/date?date=${today}`) + authFetch(`/api/attendance/user/date?date=${today}`) .then(r => r.json()) .then(d => setTodaySlots(d.data || [])); } else if (result.statusCode === 'INVALID_CODE') { diff --git a/frontend/src/pages/pirocheck/deposit/Deposit.js b/frontend/src/pages/pirocheck/deposit/Deposit.js index 6e2aab4..07a1ba2 100644 --- a/frontend/src/pages/pirocheck/deposit/Deposit.js +++ b/frontend/src/pages/pirocheck/deposit/Deposit.js @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { authFetch } from '../../../utils/Api'; import styles from './Deposit.module.css'; const IS_MOCK = false @@ -7,7 +8,7 @@ function Deposit() { const [deposit, setDeposit] = useState(null); useEffect(() => { - fetch('/api/deposit/me') + authFetch('/api/deposit/me') .then(r => r.json()) .then(data => setDeposit(data)) .catch(() => {}); diff --git a/frontend/src/pages/pirocheck/students/StudentDetail.js b/frontend/src/pages/pirocheck/students/StudentDetail.js index 6c31628..469c2f7 100644 --- a/frontend/src/pages/pirocheck/students/StudentDetail.js +++ b/frontend/src/pages/pirocheck/students/StudentDetail.js @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { useParams, useLocation } from 'react-router-dom'; +import { authFetch } from '../../../utils/Api'; import styles from './StudentDetail.module.css'; import ProfileImg from '../../../assets/images/profile.svg'; import Logo2 from '../../../assets/images/logo2.svg'; @@ -191,7 +192,7 @@ function StudentDetail() { // TODO: GET /api/admin/admin/student/{userId}/status/{week} (1~5주차) // 커리큘럼에서 세션 제목 가져오기 - const curriculumRes = await fetch('/api/curriculums'); + const curriculumRes = await authFetch('/api/curriculums'); const curriculums = await curriculumRes.json(); // weeks 데이터에 sessionTitles 추가 diff --git a/frontend/src/pages/pirocheck/students/StudentList.js b/frontend/src/pages/pirocheck/students/StudentList.js index 64d971b..802bfb4 100644 --- a/frontend/src/pages/pirocheck/students/StudentList.js +++ b/frontend/src/pages/pirocheck/students/StudentList.js @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { authFetch } from '../../../utils/Api'; import styles from './StudentList.module.css'; import ArrowRight from '../../../assets/images/icon_arrow_right.svg'; @@ -15,7 +16,7 @@ function StudentList() { const url = keyword ? `/api/admin/studentlist/search?name=${keyword}` : '/api/admin/studentlist'; - const res = await fetch(url); + const res = await authFetch(url); const data = await res.json(); setStudents(Array.isArray(data) ? data : data.data || []); } catch (e) {} diff --git a/frontend/src/utils/Api.js b/frontend/src/utils/Api.js new file mode 100644 index 0000000..91cdd7c --- /dev/null +++ b/frontend/src/utils/Api.js @@ -0,0 +1,11 @@ +export function authFetch(url, options = {}) { + const token = localStorage.getItem('token'); + return fetch(url, { + ...options, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + ...options.headers, + }, + }); +} \ No newline at end of file From a7ae3c6e45b1f60b35fc8d029e9374712f5ce72a Mon Sep 17 00:00:00 2001 From: plumbestie Date: Sun, 24 May 2026 15:43:19 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[Feat]=20Assignment=20css=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/pages/pirocheck/assignment/Assignment.js | 3 ++- frontend/src/pages/pirocheck/assignment/Assignment.module.css | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/pirocheck/assignment/Assignment.js b/frontend/src/pages/pirocheck/assignment/Assignment.js index aa2aaf2..81a0f13 100644 --- a/frontend/src/pages/pirocheck/assignment/Assignment.js +++ b/frontend/src/pages/pirocheck/assignment/Assignment.js @@ -160,8 +160,9 @@ function Assignment() { const handleDelete = async (assignmentId) => { if (!window.confirm('삭제하시겠습니까?')) return; + await authFetch(`/api/assignments/${assignmentId}`, { method: 'DELETE' }); fetchAll(); - }; + }; return (
diff --git a/frontend/src/pages/pirocheck/assignment/Assignment.module.css b/frontend/src/pages/pirocheck/assignment/Assignment.module.css index 8e38911..5ca20b9 100644 --- a/frontend/src/pages/pirocheck/assignment/Assignment.module.css +++ b/frontend/src/pages/pirocheck/assignment/Assignment.module.css @@ -103,7 +103,7 @@ .dayLabel { color: var(--dark); - font-family: var(--font-title); + font-family: var(--font-main); font-size: 1.2rem; font-weight: 700; } @@ -212,7 +212,7 @@ } .modalTitle { - font-family: var(--font-title); + font-family: var(--font-main); font-size: 3rem; font-weight: 800; color: var(--main);