diff --git a/frontend/src/components/attendancemanage/AbsenceSummaryModal.jsx b/frontend/src/components/attendancemanage/AbsenceSummaryModal.jsx
index 9e6da871..29c12e09 100644
--- a/frontend/src/components/attendancemanage/AbsenceSummaryModal.jsx
+++ b/frontend/src/components/attendancemanage/AbsenceSummaryModal.jsx
@@ -1,14 +1,14 @@
-import React from 'react';
+import { createPortal } from 'react-dom';
import styles from './AbsenceSummaryModal.module.css';
const AbsenceSummaryModal = ({ isOpen, onClose, userRows }) => {
if (!isOpen) return null;
// 결석한 기록이 있는 유저들만 필터링하고 결석 횟수 계산
- const absenceData = userRows
+ const absenceData = (userRows || [])
.map(user => {
- const totalAbsences = user.attendances.filter(att => att.status === 'ABSENT').length;
- const totalLates = user.attendances.filter(att => att.status === 'LATE').length;
+ const totalAbsences = (user.attendances || []).filter(att => att.status === 'ABSENT').length;
+ const totalLates = (user.attendances || []).filter(att => att.status === 'LATE').length;
return {
...user,
totalAbsences,
@@ -18,7 +18,7 @@ const AbsenceSummaryModal = ({ isOpen, onClose, userRows }) => {
.filter(user => user.totalAbsences > 0 || user.totalLates > 0)
.sort((a, b) => b.totalAbsences - a.totalAbsences || b.totalLates - a.totalLates);
- return (
+ const modalContent = (
);
+
+ return createPortal(modalContent, document.body);
};
export default AbsenceSummaryModal;
diff --git a/frontend/src/components/attendancemanage/AbsenceSummaryModal.module.css b/frontend/src/components/attendancemanage/AbsenceSummaryModal.module.css
index 343a8dd8..ab2d12ad 100644
--- a/frontend/src/components/attendancemanage/AbsenceSummaryModal.module.css
+++ b/frontend/src/components/attendancemanage/AbsenceSummaryModal.module.css
@@ -67,6 +67,7 @@
padding: 24px 28px;
overflow-y: auto;
flex: 1;
+ max-height: 100%;
}
.summaryTable {
@@ -75,6 +76,8 @@
}
.summaryTable th {
+ position: sticky;
+ top: -24px; /* modalBody padding-top 보정 */
text-align: left;
padding: 12px 14px;
font-size: 14px;
@@ -82,6 +85,7 @@
color: #64748b;
background: #f8fafc;
border-bottom: 2px solid #e2e8f0;
+ z-index: 10;
}
.summaryTable td {
diff --git a/frontend/src/components/attendancemanage/AttendanceManagementCard.jsx b/frontend/src/components/attendancemanage/AttendanceManagementCard.jsx
index 11062bde..a00352af 100644
--- a/frontend/src/components/attendancemanage/AttendanceManagementCard.jsx
+++ b/frontend/src/components/attendancemanage/AttendanceManagementCard.jsx
@@ -157,8 +157,8 @@ const AttendanceManagementCard = ({ styles: commonStyles }) => {
const fetchRequestIdRef = useRef(0);
const sortedUserRows = useMemo(() => {
- if (!attendanceData.rounds.length) {
- return attendanceData.userRows
+ if (!(attendanceData?.rounds || []).length) {
+ return (attendanceData?.userRows || [])
.map((user, index) => ({ user, index }))
.sort((a, b) => {
const priorityDiff =
@@ -170,9 +170,9 @@ const AttendanceManagementCard = ({ styles: commonStyles }) => {
}
// 로컬 시간대 기준으로 오늘 날짜(YYYY-MM-DD)와 정렬된 회차 목록 생성
- const sortedRounds = [...attendanceData.rounds].sort(
+ const sortedRounds = [...(attendanceData?.rounds || [])].sort(
(a, b) =>
- a.roundDate.localeCompare(b.roundDate) || a.roundNumber - b.roundNumber
+ (a.roundDate || '').localeCompare(b.roundDate || '') || (a.roundNumber || 0) - (b.roundNumber || 0)
);
const todayStr = new Date().toLocaleDateString('sv-SE');
@@ -181,10 +181,9 @@ const AttendanceManagementCard = ({ styles: commonStyles }) => {
sortedRounds[sortedRounds.length - 1];
const targetRoundId = targetRound?.roundId;
-
- return [...attendanceData.userRows].sort((a, b) => {
- const aAtt = a.attendances.find((att) => att.roundId === targetRoundId);
- const bAtt = b.attendances.find((att) => att.roundId === targetRoundId);
+ return [...(attendanceData?.userRows || [])].sort((a, b) => {
+ const aAtt = (a?.attendances || []).find((att) => att.roundId === targetRoundId);
+ const bAtt = (b?.attendances || []).find((att) => att.roundId === targetRoundId);
const aStatus = aAtt?.status || 'PENDING';
const bStatus = bAtt?.status || 'PENDING';
@@ -513,7 +512,7 @@ const AttendanceManagementCard = ({ styles: commonStyles }) => {
이름 |
역할 |
학번 |
- {attendanceData.rounds.map((round) => (
+ {(attendanceData?.rounds || []).map((round) => (
{round.roundNumber}회차
|
@@ -540,7 +539,7 @@ const AttendanceManagementCard = ({ styles: commonStyles }) => {
{user.userName} |
{getRoleDisplayLabel(user.role)} |
{user.studentId} |
- {user.attendances.map((att) => {
+ {(user.attendances || []).map((att) => {
const statusClass =
styles[
ATTENDANCE_CONFIG[att.status]?.className ||
@@ -585,7 +584,7 @@ const AttendanceManagementCard = ({ styles: commonStyles }) => {
) : (
|
데이터가 존재하지 않습니다.
diff --git a/frontend/src/components/attendancemanage/SessionManagementCard.jsx b/frontend/src/components/attendancemanage/SessionManagementCard.jsx
index 0a3a4463..7a2f182c 100644
--- a/frontend/src/components/attendancemanage/SessionManagementCard.jsx
+++ b/frontend/src/components/attendancemanage/SessionManagementCard.jsx
@@ -77,7 +77,12 @@ const SessionManagementCard = ({ styles: commonStyles }) => {
const rounds = await getRounds(selectedSessionId);
setCurrentDisplayedRounds(rounds || []);
} catch (e) {
- toast.error('라운드를 불러오지 못했습니다.');
+ const status = e?.response?.status ?? e?.status;
+ if (status === 403) {
+ toast.error('세션 멤버가 아니거나 조회 권한이 없습니다.');
+ } else {
+ toast.error('라운드를 불러오지 못했습니다.');
+ }
setCurrentDisplayedRounds([]);
}
};
@@ -214,8 +219,8 @@ const SessionManagementCard = ({ styles: commonStyles }) => {
|
- {currentDisplayedRounds.length > 0 ? (
- currentDisplayedRounds.map((round, index) => {
+ {(currentDisplayedRounds || []).length > 0 ? (
+ (currentDisplayedRounds || []).map((round, index) => {
const startTime = new Date(round.startAt);
const closeTime = new Date(round.closeAt);
diff --git a/frontend/src/components/attendancemanage/SessionManagementCard.module.css b/frontend/src/components/attendancemanage/SessionManagementCard.module.css
index 0780be4e..702149af 100644
--- a/frontend/src/components/attendancemanage/SessionManagementCard.module.css
+++ b/frontend/src/components/attendancemanage/SessionManagementCard.module.css
@@ -515,7 +515,6 @@
width: 100%;
min-width: 0;
overflow-x: auto;
- overflow-y: hidden;
padding-bottom: 2px;
margin-left: 0;
}