Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "piroinfront",
"proxy": "http://localhost:8080",
"version": "0.1.0",
"private": true,
"dependencies": {
Expand All @@ -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",
Expand Down
13 changes: 9 additions & 4 deletions frontend/src/pages/OnboardingPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/pages/login/LoginPage.js
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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),
Expand Down
1 change: 0 additions & 1 deletion frontend/src/pages/pirocheck/PIroCheckMain.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import styles from './PIroCheckMain.module.css';

Expand Down
39 changes: 6 additions & 33 deletions frontend/src/pages/pirocheck/assignment/Assignment.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,26 +15,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 }) {
Expand Down Expand Up @@ -69,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 }),
Expand Down Expand Up @@ -164,13 +146,9 @@ 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}`)
authFetch(`/api/assignments/me/${w}`)
.then(r => r.json())
.catch(() => ({ week: w, assignments: [] }))
)
Expand All @@ -182,17 +160,12 @@ function Assignment() {

const handleDelete = async (assignmentId) => {
if (!window.confirm('삭제하시겠습니까?')) return;
if (!IS_MOCK) {
await fetch(`/api/assignments/${assignmentId}`, { method: 'DELETE' });
}
await authFetch(`/api/assignments/${assignmentId}`, { method: 'DELETE' });
fetchAll();
};
};

return (
<div className={styles.container}>
{IS_MOCK && (
<div className={styles.mockBanner}>⚠️ 현재 임시 데이터로 표시 중입니다. 백엔드 연동 후 IS_MOCK을 false로 변경하세요.</div>
)}
<div className={styles.title}>ASSIGNMENT CHECK</div>

{weeks.map((w, i) => (
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/pirocheck/assignment/Assignment.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -212,7 +212,7 @@
}

.modalTitle {
font-family: var(--font-title);
font-family: var(--font-main);
font-size: 3rem;
font-weight: 800;
color: var(--main);
Expand Down
27 changes: 14 additions & 13 deletions frontend/src/pages/pirocheck/attendance/Attendance.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,8 +15,11 @@ function cloverForSlot(status) {
return <img src={CloverEmpty} className={styles.cloverSvg} alt="미정" />;
}

function slotIcon(status) {
if (status === true) return <img src={Coin1} className={styles.histSvg} alt="출석" />;
function historyIcon(slots) {
const successCount = slots.filter(s => s.status === true).length;
if (successCount === 3) return <img src={Coin3} className={styles.histSvg} alt="3회 출석" />;
if (successCount === 2) return <img src={Coin2} className={styles.histSvg} alt="2회 출석" />;
if (successCount === 1) return <img src={Coin1} className={styles.histSvg} alt="1회 출석" />;
return <img src={AngryIcon} className={styles.histSvg} alt="결석" />;
}

Expand All @@ -27,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) {
Expand All @@ -41,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);
};
Expand All @@ -72,7 +76,7 @@ function AdminView() {
종료
</button>
)}
<a className={styles.manageLink} href="/attendance/manage">출석 관리</a>
<a className={styles.manageLink} href="/pirocheck/students">출석 관리</a>
</div>
</>
);
Expand All @@ -96,7 +100,7 @@ function MemberView() {
]
}));

fetch('/api/attendance/user')
authFetch('/api/attendance/user')
.then(r => r.json())
.then(data => {
const apiData = data.data || [];
Expand All @@ -112,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 }),
Expand All @@ -123,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') {
Expand Down Expand Up @@ -166,10 +170,7 @@ function MemberView() {
<div key={i} className={styles.historyRow}>
<span className={styles.weekLabel}>{row.week}주차</span>
<div className={styles.historySlots}>
{[0, 1, 2].map(j => {
const slot = row.slots[j] ?? { status: false };
return <div key={j}>{slotIcon(slot.status)}</div>;
})}
{historyIcon(row.slots)}
</div>
</div>
))}
Expand Down
25 changes: 4 additions & 21 deletions frontend/src/pages/pirocheck/deposit/Deposit.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,23 @@
import { useState, useEffect } from 'react';
import { authFetch } from '../../../utils/Api';
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')
authFetch('/api/deposit/me')
.then(r => r.json())
.then(data => setDeposit(data))
.catch(() => setDeposit(MOCK_DATA));
.catch(() => {});
}, []);

if (!deposit) return null;

return (
<div className={styles.container}>
{IS_MOCK && (
<div className={styles.mockBanner}>
⚠️ 현재 임시 데이터로 표시 중입니다. 백엔드 연동 후 IS_MOCK을 false로 변경하세요.
</div>
)}

<div className={styles.title}>DEPOSIT CHECK</div>

<div className={styles.amountBox}>
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/pages/pirocheck/students/StudentDetail.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 추가
Expand Down
38 changes: 10 additions & 28 deletions frontend/src/pages/pirocheck/students/StudentList.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
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';

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();
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);
try {
const url = keyword
? `/api/admin/studentlist/search?name=${keyword}`
: '/api/admin/studentlist';
const res = await authFetch(url);
const data = await res.json();
setStudents(Array.isArray(data) ? data : data.data || []);
} catch (e) {}
};

useEffect(() => { fetchStudents(); }, []);
Expand All @@ -41,11 +28,6 @@ function StudentList() {

return (
<div className={styles.container}>
{IS_MOCK && (
<div className={styles.mockBanner}>
⚠️ 현재 임시 데이터로 표시 중입니다.
</div>
)}

<div className={styles.title}>PIROGRAMMER</div>

Expand Down
11 changes: 11 additions & 0 deletions frontend/src/utils/Api.js
Original file line number Diff line number Diff line change
@@ -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,
},
});
}
Loading