Skip to content

Commit 2bbca4c

Browse files
yyuneesangkyu39
andauthored
20260403 fe add adminpage absent list (#351)
* [FE]피드백 모달 수정 * [FIX] Scroll error in chrome * [FIX] rabbit review * [FIX] scroll error * [FIX] Rabbit review * [FIX] Checkin before login * [ADD]absent list modal & sort in attendance admin page * [FIX] Rabbit Review --------- Co-authored-by: sangkyu39 <sangkyu.p39@gmail.com>
1 parent ebb967b commit 2bbca4c

5 files changed

Lines changed: 377 additions & 69 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React from 'react';
2+
import styles from './AbsenceSummaryModal.module.css';
3+
4+
const AbsenceSummaryModal = ({ isOpen, onClose, userRows }) => {
5+
if (!isOpen) return null;
6+
7+
// 결석한 기록이 있는 유저들만 필터링하고 결석 횟수 계산
8+
const absenceData = userRows
9+
.map(user => {
10+
const totalAbsences = user.attendances.filter(att => att.status === 'ABSENT').length;
11+
const totalLates = user.attendances.filter(att => att.status === 'LATE').length;
12+
return {
13+
...user,
14+
totalAbsences,
15+
totalLates
16+
};
17+
})
18+
.filter(user => user.totalAbsences > 0 || user.totalLates > 0)
19+
.sort((a, b) => b.totalAbsences - a.totalAbsences || b.totalLates - a.totalLates);
20+
21+
return (
22+
<div className={styles.modalOverlay} onClick={onClose}>
23+
<div
24+
className={styles.modalContent}
25+
onClick={(e) => e.stopPropagation()}
26+
role="dialog"
27+
aria-modal="true"
28+
aria-labelledby="absence-modal-title"
29+
>
30+
<div className={styles.modalHeader}>
31+
<h2 id="absence-modal-title">결석 및 지각 집계</h2>
32+
<button
33+
type="button"
34+
className={styles.closeButton}
35+
onClick={onClose}
36+
aria-label="닫기"
37+
>
38+
&times;
39+
</button>
40+
</div>
41+
<div className={styles.modalBody}>
42+
{absenceData.length > 0 ? (
43+
<table className={styles.summaryTable}>
44+
<thead>
45+
<tr>
46+
<th>이름</th>
47+
<th>학번</th>
48+
<th>총 결석</th>
49+
<th>총 지각</th>
50+
</tr>
51+
</thead>
52+
<tbody>
53+
{absenceData.map((user) => (
54+
<tr key={user.userId}>
55+
<td>{user.userName}</td>
56+
<td>{user.studentId}</td>
57+
<td className={styles.absentCount}>
58+
{user.totalAbsences}
59+
</td>
60+
<td className={styles.lateCount}>{user.totalLates}</td>
61+
</tr>
62+
))}
63+
</tbody>
64+
</table>
65+
) : (
66+
<p className={styles.noData}>
67+
결석 또는 지각 기록이 있는 학생이 없습니다.
68+
</p>
69+
)}
70+
</div>
71+
<div className={styles.modalFooter}>
72+
<button
73+
type="button"
74+
className={styles.confirmButton}
75+
onClick={onClose}
76+
>
77+
확인
78+
</button>
79+
</div>
80+
</div>
81+
</div>
82+
);
83+
};
84+
85+
export default AbsenceSummaryModal;
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
.modalOverlay {
2+
position: fixed;
3+
top: 0;
4+
left: 0;
5+
width: 100vw;
6+
height: 100vh;
7+
background-color: rgba(0, 0, 0, 0.45);
8+
display: flex;
9+
align-items: center;
10+
justify-content: center;
11+
z-index: 9999;
12+
backdrop-filter: blur(4px);
13+
}
14+
15+
.modalContent {
16+
background-color: #ffffff;
17+
border-radius: 16px;
18+
width: 90%;
19+
max-width: 540px;
20+
max-height: 85vh;
21+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15);
22+
display: flex;
23+
flex-direction: column;
24+
animation: modalAppear 0.25s ease-out;
25+
}
26+
27+
@keyframes modalAppear {
28+
from {
29+
opacity: 0;
30+
transform: translateY(15px);
31+
}
32+
to {
33+
opacity: 1;
34+
transform: translateY(0);
35+
}
36+
}
37+
38+
.modalHeader {
39+
padding: 24px 28px;
40+
border-bottom: 1px solid #f1f3f7;
41+
display: flex;
42+
justify-content: space-between;
43+
align-items: center;
44+
}
45+
46+
.modalHeader h2 {
47+
margin: 0;
48+
font-size: 20px;
49+
font-weight: 700;
50+
color: #1e293b;
51+
}
52+
53+
.closeButton {
54+
background: none;
55+
border: none;
56+
font-size: 28px;
57+
color: #64748b;
58+
cursor: pointer;
59+
transition: color 0.15s;
60+
}
61+
62+
.closeButton:hover {
63+
color: #1e293b;
64+
}
65+
66+
.modalBody {
67+
padding: 24px 28px;
68+
overflow-y: auto;
69+
flex: 1;
70+
}
71+
72+
.summaryTable {
73+
width: 100%;
74+
border-collapse: collapse;
75+
}
76+
77+
.summaryTable th {
78+
text-align: left;
79+
padding: 12px 14px;
80+
font-size: 14px;
81+
font-weight: 600;
82+
color: #64748b;
83+
background: #f8fafc;
84+
border-bottom: 2px solid #e2e8f0;
85+
}
86+
87+
.summaryTable td {
88+
padding: 14px;
89+
font-size: 15px;
90+
color: #334155;
91+
border-bottom: 1px solid #f1f3f7;
92+
}
93+
94+
.absentCount {
95+
color: #ef4444;
96+
font-weight: 700;
97+
}
98+
99+
.lateCount {
100+
color: #9333ea;
101+
font-weight: 700;
102+
}
103+
104+
.noData {
105+
text-align: center;
106+
color: #64748b;
107+
font-size: 15px;
108+
margin: 40px 0;
109+
}
110+
111+
.modalFooter {
112+
padding: 20px 28px;
113+
border-top: 1px solid #f1f3f7;
114+
display: flex;
115+
justify-content: flex-end;
116+
}
117+
118+
.confirmButton {
119+
background-color: #2563eb;
120+
color: #ffffff;
121+
padding: 10px 24px;
122+
border: none;
123+
border-radius: 8px;
124+
font-size: 15px;
125+
font-weight: 600;
126+
cursor: pointer;
127+
transition: background-color 0.15s;
128+
}
129+
130+
.confirmButton:hover {
131+
background-color: #1d4ed8;
132+
}
133+
134+
@media (max-width: 640px) {
135+
.modalContent {
136+
width: 95%;
137+
}
138+
139+
.modalHeader, .modalBody, .modalFooter {
140+
padding: 16px 20px;
141+
}
142+
}

0 commit comments

Comments
 (0)