Skip to content
17 changes: 17 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,31 @@ 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";
<<<<<<< HEAD
import AdminStudentAssignment from "./pages/admin/AdminStudentAssignment.jsx";
=======
import RequireAuth from "./components/RequireAuth";
import RequireAdmin from "./components/RequireAdmin";

>>>>>>> 08242a5045ea08b68c40b107cc871f8b3c3446eb
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Intro />} />
<Route path="/login" element={<Login />} />
<<<<<<< HEAD
<Route path="/home" element={<Home />} />
<Route path="/assignment" element={<Assignment />} />
<Route path="/attendance" element={<Attendance />} />
<Route path="/deposit" element={<Deposit />} />
<Route path="/admin" element={<Admin />} />
<Route path="/magagestudent" element={<MagageStudent />} />
<Route path="/magagetask" element={<ManageTask />} />
<Route path="/attendancecode" element={<AttendanceCode />} />
<Route path="/admin/attendance/:studentId" element={<AdminStudentAttendance />} />
<Route path="/admin/managestudent/:studentId" element={<AdminStudentAssignment />} />
=======
<Route
path="/home"
element={
Expand Down Expand Up @@ -101,6 +117,7 @@ function App() {
</RequireAdmin>
}
/>
>>>>>>> 08242a5045ea08b68c40b107cc871f8b3c3446eb
</Routes>
</BrowserRouter>
);
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/api/adminattendance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import api from "./api";

// api/attendanceApi.js

export const getStudentBasicInfo = async (studentId) => {
try {
const res = await api.get(`/admin/managestudent/${studentId}`);
return res.data;
} catch (error) {
console.error("학생 기본 정보 불러오기 실패:", error);
throw error;
}
};

export const getStudentAttendance = async (studentId) => {
try {
const res = await api.get("/admin/attendance/user", {
params: { userId: studentId },
withCredentials: true,
});
return res.data;
} catch (error) {
console.error("학생 출석 정보 불러오기 실패:", error);
throw error;
}
};
2 changes: 0 additions & 2 deletions frontend/src/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import axios from "axios";

const api = axios.create({
baseURL: "http://api.pirocheck.org:8080/api",

// 수정 필요한지 재검 필요함
// "http://api.pirocheck.org:8080/api"

withCredentials: true,
});

Expand Down
12 changes: 11 additions & 1 deletion frontend/src/api/assignment.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import api from "./api";

/*
export const fetchAssignmentsByUser = async (userId) => {
const res = await api.get(`/assignment/grouped/${userId}`);
return res.data;
};
*/
export const fetchAssignmentsByUser = async (userId) => {
try {
const res = await api.get(`/api/assignment/${userId}`);
return res.data; // 백엔드가 반환하는 JSON 그대로
} catch (err) {
console.error("과제 데이터 불러오기 실패:", err);
throw err;
}
};
4 changes: 1 addition & 3 deletions frontend/src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ const Header = () => {
height={30}
/>
</button>
) : (
<div style={{ width: "30px" }} />
)}
) : null}
{showRightMagageStudent ? (
<button
className="icon-button"
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/componentsCss/Header.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
display: flex;
align-items: center;
width: 390px;
justify-content: space-between;
justify-content: flex-start;
padding: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
gap: 100px;
box-shadow: 0 2px 4px #0003;
margin: 10px;
}

Expand Down
125 changes: 125 additions & 0 deletions frontend/src/pages/admin/AdminStudentAssignment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import AdminStudentHeader from "../../components/AdminStudentHeader";
import WeeklyOpenBlock from "../../components/WeeklyOpenBlock";
import AssignmentInfoBlock from "../../components/AssignmentInfoBlock";
import api from "../../api/api";
import styles from "./AdminStudentAssignment.module.css";

const AdminStudentAssignment = () => {
const { studentId, week } = useParams();
const [studentInfo, setStudentInfo] = useState(null);
const [weeks, setWeeks] = useState([]);
const [highlightCard, setHighlightCard] = useState(null);
const [selectedWeekLabel, setSelectedWeekLabel] = useState(null);

useEffect(() => {
api.get(`/admin/users/${studentId}`).then((res) => {
setStudentInfo(res.data.data);
});

api
.get(`/admin/managestudent/{studentId}`, {
params: { userId: studentId },
withCredentials: true,
})
.then((res) => {
const formatted = res.data.data.map((weekItem) => ({
week: weekItem.week,
label: `${weekItem.week}주차 ${weekItem.title}`,
days: weekItem.days.map((dayItem) => ({
day: dayItem.day,
subject: weekItem.title,
tasks: dayItem.details.map((task) => ({
id: task.id,
label: task.assignmentName,
status: task.status,
modified: false,
})),
})),
}));

setWeeks(formatted);

const matched = formatted.find((w) => String(w.week) === String(week));
if (matched) {
setSelectedWeekLabel(matched.label);
if (matched.days.length > 0) {
setHighlightCard({
weekLabel: matched.label,
day: matched.days[0].day,
tasks: matched.days[0].tasks,
});
}
}
});
}, [studentId, week]);

const handleStatusChange = (weekIdx, dayIdx, taskIdx, newStatus) => {
const updated = [...weeks];
const task = updated[weekIdx].days[dayIdx].tasks[taskIdx];
task.status = newStatus;
task.modified = true;
setWeeks(updated);
};

const handleSave = async (taskId, status) => {
await api.put("/admin/assignment/status", {
assignmentId: taskId,
status,
});
};

return (
<div className={styles.container}>
<AdminStudentHeader
studentName={`${studentInfo?.name || "이름 없음"} ${selectedWeekLabel ? `- ${selectedWeekLabel}` : ""}`}
onBack={() => window.history.back()}
/>

{highlightCard && (
<div className={styles.info}>
<AssignmentInfoBlock {...highlightCard} />
</div>
)}

<div className={styles.weekList}>
{weeks.map((weekItem, weekIdx) => (
<div className={styles.weekBlock} key={weekIdx}>
<p className={styles.weekTitle}>{weekItem.label}</p>
{weekItem.days.map((dayItem, dayIdx) => (
<div key={dayIdx} className={styles.dayCard}>
<p className={styles.dayLabel}>{dayItem.day} &nbsp; {dayItem.subject}</p>
<div className={styles.taskList}>
{dayItem.tasks.map((task, taskIdx) => (
<div key={task.id} className={styles.taskRow}>
<span className={styles.taskLabel}>{task.label}</span>
<select
value={task.status}
onChange={(e) => handleStatusChange(weekIdx, dayIdx, taskIdx, e.target.value)}
>
<option value="SUCCESS">성공</option>
<option value="INSUFFICIENT">미흡</option>
<option value="FAILURE">실패</option>
</select>
<button
className={styles.saveButton}
disabled={!task.modified}
onClick={() => handleSave(task.id, task.status)}
>
save
</button>
</div>
))}
</div>
<button className={styles.submitBtn}>submit</button>
</div>
))}
</div>
))}
</div>
</div>
);
};

export default AdminStudentAssignment;
112 changes: 112 additions & 0 deletions frontend/src/pages/admin/AdminStudentAssignment.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
.container {
display: flex;
flex-direction: column;
padding: 20px;
font-family: "Inter", sans-serif;
color: white;
background-color: #1e1e1e;
}

/* 과제 개요 카드 (상단 형광 카드) */
.info {
background-color: #045e07;
border-radius: 12px;
padding: 16px;
margin-bottom: 24px;
}

/* 주차별 목록 */
.weekList {
display: flex;
flex-direction: column;
gap: 24px;
}

/* 주차 구간 */
.weekBlock {
border-left: 4px solid #00c851;
background-color: #2d2d2d;
border-radius: 10px;
padding: 16px;
}

.weekTitle {
font-size: 18px;
font-weight: bold;
color: #00ff99;
margin-bottom: 12px;
}

/* 요일별 카드 */
.dayCard {
background-color: #3a3a3a;
padding: 12px 16px;
border-radius: 10px;
margin-bottom: 16px;
}

.dayLabel {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}

/* 개별 과제 */
.taskList {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 12px;
}

.taskRow {
display: flex;
align-items: center;
gap: 10px;
background-color: #505050;
border-radius: 6px;
padding: 8px 12px;
}

.taskLabel {
flex: 1;
font-size: 14px;
color: white;
}

/* 드롭다운 */
.taskRow select {
padding: 6px 8px;
border-radius: 6px;
background-color: #2a2a2a;
color: white;
border: 1px solid #777;
}

/* save 버튼 */
.saveButton {
padding: 4px 10px;
border-radius: 6px;
background-color: #00c851;
color: white;
font-weight: bold;
border: none;
cursor: pointer;
}

.saveButton:disabled {
background-color: gray;
cursor: not-allowed;
}

/* submit 버튼 */
.submitBtn {
margin-top: 8px;
padding: 6px 12px;
border-radius: 8px;
background-color: #1fa067;
color: white;
font-weight: bold;
border: none;
cursor: pointer;
}
Loading