diff --git a/frontend/src/App.js b/frontend/src/App.js
index 7c37e68..dc6becf 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -8,6 +8,7 @@ import QnADetailPage from './pages/qna/QnADetailPage';
import CurriculumPage from './pages/curriculum/CurriculumPage';
import PiroCheckMain from './pages/pirocheck/PIroCheckMain';
import Attendance from './pages/pirocheck/attendance/Attendance'
+import Assignment from './pages/pirocheck/assignment/Assignment';
function App() {
return (
@@ -29,8 +30,9 @@ function App() {
{/* 다크 헤더 페이지 */}
}>
- } />
- } />
+ }/>
+ }/>
+ }/>
diff --git a/frontend/src/assets/images/icon_delete.svg b/frontend/src/assets/images/icon_delete.svg
new file mode 100644
index 0000000..da16964
--- /dev/null
+++ b/frontend/src/assets/images/icon_delete.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/images/icon_edit.svg b/frontend/src/assets/images/icon_edit.svg
new file mode 100644
index 0000000..a0788a2
--- /dev/null
+++ b/frontend/src/assets/images/icon_edit.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/images/icon_status_o.svg b/frontend/src/assets/images/icon_status_o.svg
new file mode 100644
index 0000000..87e1cf5
--- /dev/null
+++ b/frontend/src/assets/images/icon_status_o.svg
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/frontend/src/assets/images/icon_status_triangle.svg b/frontend/src/assets/images/icon_status_triangle.svg
new file mode 100644
index 0000000..4764cf3
--- /dev/null
+++ b/frontend/src/assets/images/icon_status_triangle.svg
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/frontend/src/assets/images/icon_status_x.svg b/frontend/src/assets/images/icon_status_x.svg
new file mode 100644
index 0000000..8152f92
--- /dev/null
+++ b/frontend/src/assets/images/icon_status_x.svg
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/frontend/src/pages/pirocheck/assignment/Assignment.js b/frontend/src/pages/pirocheck/assignment/Assignment.js
new file mode 100644
index 0000000..9f1644a
--- /dev/null
+++ b/frontend/src/pages/pirocheck/assignment/Assignment.js
@@ -0,0 +1,223 @@
+import { useState, useEffect } from 'react';
+import styles from './Assignment.module.css';
+import LogoImg from '../../../assets/images/logo.png';
+import EditIcon from '../../../assets/images/icon_edit.svg';
+import DeleteIcon from '../../../assets/images/icon_delete.svg';
+import StatusO from '../../../assets/images/icon_status_o.svg';
+import StatusT from '../../../assets/images/icon_status_triangle.svg';
+import StatusX from '../../../assets/images/icon_status_x.svg';
+
+// 요일 변환
+const dayMap = {
+ TUESDAY: 'TUE',
+ THURSDAY: 'THU',
+ 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로 변경
+
+// 제출 상태 아이콘 (부원용)
+function StatusIcon({ status }) {
+ if (status === 'SUBMITTED') return
;
+ if (status === 'LATE') return
;
+ return
;
+}
+
+// 세션별 과제 묶기
+function groupByDay(assignments) {
+ const order = ['TUESDAY', 'THURSDAY', 'SATURDAY'];
+ const grouped = {};
+ assignments.forEach(a => {
+ if (!grouped[a.day]) grouped[a.day] = { day: a.day, sessionDate: a.sessionDate, items: [] };
+ grouped[a.day].items.push(a);
+ });
+ return order.filter(d => grouped[d]).map(d => grouped[d]);
+}
+
+// ── 과제 등록/수정 모달 ───────────────────────────────
+function AssignmentModal({ item, onClose, onSave }) {
+ const isEdit = !!item;
+ const [form, setForm] = useState({
+ week: item?.week || '1',
+ day: item?.day || 'TUESDAY',
+ title: item?.title || '',
+ });
+ const weeks = ['1', '2', '3', '4', '5'];
+ const days = ['TUESDAY', 'THURSDAY', 'SATURDAY'];
+
+ const handleSave = async () => {
+ const url = isEdit
+ ? `/api/assignments/modify/${item.assignmentId}`
+ : '/api/assignments/create';
+ const method = isEdit ? 'PATCH' : 'POST';
+ await fetch(url, {
+ method,
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ title: form.title, week: form.week, day: form.day }),
+ });
+ onSave();
+ onClose();
+ };
+
+ return (
+
+
+

+
ASSIGNMENT
+
+
+
+ 과제
+
+
setForm({ ...form, title: e.target.value })}
+ />
+
+
+
+ );
+}
+
+// ── 주차 블록 (공통) ──────────────────────────────────
+function WeekBlock({ weekData, role, onEdit, onDelete }) {
+ const [isOpen, setIsOpen] = useState(false);
+ const grouped = groupByDay(weekData.assignments || []);
+
+ return (
+
+
setIsOpen(prev => !prev)}>
+
+

+
WEEK {weekData.week}
+
+
{isOpen ? '▲' : '▼'}
+
+
+ {isOpen && (
+
+ {grouped.length === 0 && (
+
등록된 과제가 없습니다.
+ )}
+ {grouped.map((session, j) => (
+
+
+
+ {dayMap[session.day]}
+ {session.sessionDate && {session.sessionDate}}
+
+ {role === 'ADMIN' && (
+
+
+
+
+ )}
+
+ {session.items.map((item, k) => (
+
+ {item.title}
+ {role === 'MEMBER' && }
+
+ ))}
+ {j < grouped.length - 1 &&
}
+
+ ))}
+
+ )}
+
+ );
+}
+
+// ── 메인 컴포넌트 ─────────────────────────────────────
+function Assignment() {
+ const role = localStorage.getItem('role') || 'MEMBER';
+ const [weeks, setWeeks] = useState([]);
+ 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}`)
+ .then(r => r.json())
+ .catch(() => ({ week: w, assignments: [] }))
+ )
+ );
+ setWeeks(results);
+ };
+
+ useEffect(() => { fetchAll(); }, []);
+
+ 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) => (
+
setModalItem(item)}
+ onDelete={handleDelete}
+ />
+ ))}
+
+ {role === 'ADMIN' && (
+
+ )}
+
+ {modalItem !== undefined && (
+ setModalItem(undefined)}
+ onSave={fetchAll}
+ />
+ )}
+
+ );
+}
+
+export default Assignment;
\ No newline at end of file
diff --git a/frontend/src/pages/pirocheck/assignment/Assignment.module.css b/frontend/src/pages/pirocheck/assignment/Assignment.module.css
new file mode 100644
index 0000000..8e38911
--- /dev/null
+++ b/frontend/src/pages/pirocheck/assignment/Assignment.module.css
@@ -0,0 +1,287 @@
+.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;
+ text-align: center;
+ width: 600px;
+ box-sizing: border-box;
+}
+
+.title {
+ font-family: var(--font-title);
+ font-size: 3rem;
+ font-weight: 800;
+ color: var(--main);
+ margin-bottom: 40px;
+ letter-spacing: 0;
+}
+
+/* 주차 블록 */
+.weekBlock {
+ width: 600px;
+ background: #3a3a3a;
+ border-radius: 16px;
+ margin-bottom: 20px;
+ overflow: hidden;
+}
+
+.weekHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 20px 24px;
+ cursor: pointer;
+ color: var(--white);
+}
+
+.weekLeft {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.logoIcon {
+ width: 28px;
+ height: 28px;
+ object-fit: contain;
+}
+
+.weekLabel {
+ font-family: var(--font-main);
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: var(--white);
+}
+
+.arrow {
+ color: var(--white);
+ font-size: 0.9rem;
+}
+
+.weekBody {
+ padding: 0 24px 20px;
+}
+
+.empty {
+ color: #aaa;
+ font-family: var(--font-main);
+ font-size: 0.9rem;
+ text-align: center;
+ padding: 16px 0;
+}
+
+/* 세션 */
+.session {
+ margin-top: 12px;
+}
+
+.sessionHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 8px;
+}
+
+.sessionLeft {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.dayLabel {
+ color: var(--dark);
+ font-family: var(--font-title);
+ font-size: 1.2rem;
+ font-weight: 700;
+}
+
+.sessionTitle {
+ color: var(--light);
+ font-family: var(--font-main);
+ font-size: 1rem;
+}
+
+.sessionActions {
+ display: flex;
+ gap: 8px;
+}
+
+.iconBtn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ display: flex;
+ align-items: center;
+}
+
+.actionIcon {
+ width: 20px;
+ height: 20px;
+ opacity: 0.7;
+}
+
+.actionIcon:hover { opacity: 1; }
+
+/* 과제 항목 */
+.assignmentRow {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 5px 0;
+}
+
+.assignmentTitle {
+ color: var(--white);
+ font-family: var(--font-main);
+ font-size: 0.95rem;
+}
+
+.statusIcon {
+ width: 20px;
+ height: 20px;
+}
+
+.divider {
+ border: none;
+ border-top: 1px solid #555;
+ margin: 12px 0;
+}
+
+/* + 추가 버튼 */
+.addBtn {
+ position: fixed;
+ bottom: 40px;
+ right: 40px;
+ width: 56px;
+ height: 56px;
+ border-radius: 50%;
+ background: #3a3a3a;
+ color: var(--main);
+ font-size: 2rem;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.4);
+ transition: background 0.2s;
+}
+
+.addBtn:hover { background: #4a4a4a; }
+
+/* 모달 */
+.modalOverlay {
+ position: fixed;
+ inset: 0;
+ background: var(--black);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 200;
+}
+
+.modal {
+ background: #3a3a3a;
+ border-radius: 20px;
+ padding: 40px 60px;
+ width: 420px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+}
+
+.modalLogo {
+ width: 200px;
+ height: 200px;
+ object-fit: contain;
+}
+
+.modalTitle {
+ font-family: var(--font-title);
+ font-size: 3rem;
+ font-weight: 800;
+ color: var(--main);
+ letter-spacing: 0;
+}
+
+.modalRow {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ width: 85%;
+ margin-top: 10px;
+}
+
+.select {
+ padding: 10px 36px 10px 20px;
+ background-color: var(--pale);
+
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+
+ background-image: url("data:image/svg+xml;utf8,");
+ background-repeat: no-repeat;
+ background-position: right 16px center;
+
+ border: none;
+ border-radius: 8px;
+ font-family: var(--font-main);
+ font-size: 1rem;
+ cursor: pointer;
+ flex: 1;
+}
+
+.select::-ms-expand {
+ display: none;
+}
+.modalInput {
+ width: 85%;
+ padding: 12px 20px;
+ background: var(--pale);
+ border: none;
+ border-radius: 8px;
+ font-family: var(--font-main);
+ font-size: 1rem;
+ box-sizing: border-box;
+}
+
+.modalLabel {
+ color: var(--white);
+ font-family: var(--font-main);
+ font-size: 1.2rem;
+}
+
+.saveBtn {
+ margin-top: 15px;
+ padding: 10px 45px;
+ background: transparent;
+ border: 2px solid var(--main);
+ border-radius: 10px;
+ color: var(--main);
+ font-family: var(--font-main);
+ font-size: 1rem;
+ font-weight: 650;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.saveBtn:hover {
+ background: var(--main);
+ color: var(--black);
+}
\ No newline at end of file