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
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;
}
};
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;
}
};
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;
}
27 changes: 13 additions & 14 deletions frontend/src/pages/admin/AdminStudentAttendance.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import DailyAttendanceCard from "../../components/AdminDailyAttendanceCard";
import api from "../../api/api";
import styles from "./AdminStudentAttendance.module.css";
import AdminWeeklyAttendanceList from "../../components/AdminWeeklyAttendanceList";
import { getStudentBasicInfo, getStudentAttendance } from "../../api/adminattendance";

const AdminStudentAttendance = () => {
const { studentId } = useParams();
Expand All @@ -13,22 +14,20 @@ const AdminStudentAttendance = () => {
const [selectedDate, setSelectedDate] = useState(null);

useEffect(() => {
// 1. 학생 정보 가져오기
api.get(`/admin/users/${studentId}`).then((res) => {
setStudentInfo(res.data.data);
});
const fetchData = async () => {
try {
const studentRes = await getStudentBasicInfo(studentId);
setStudentInfo(studentRes.data);

// 2. 주차별 출석 데이터 가공
api
.get("/admin/attendance/user", {
params: { userId: studentId },
withCredentials: true,
})
.then((res) => {
const raw = res.data.data;
const processed = processWeeklyAttendance(raw);
const attendanceRes = await getStudentAttendance(studentId);
const processed = processWeeklyAttendance(attendanceRes.data);
setAttendanceData(processed);
});
} catch (err) {
console.error("데이터 불러오기 실패:", err);
}
};

fetchData();
}, [studentId]);

/*
Expand Down