Skip to content

Commit bfb04d6

Browse files
[FE] [FEAT] : 출석하기(담당자) 페이지 디자인 수정 및 기능 api 연결 (#165)
* fix: CSS디자인 수정 및 유저관련 api를 제외하고 모두 연결 * feat: 세션에 사용자 추가 모달 구현 및 사용자 상태변경 기능 구현 * fix: 출석관리 페이지 디자인 수정 * fix: 출석관리(담당자) 페이지 alert문 toast로 변경 및 디자인 수정
1 parent f5632c2 commit bfb04d6

15 files changed

Lines changed: 909 additions & 225 deletions
Lines changed: 3 additions & 0 deletions
Loading

frontend/src/components/VerificationModal.module.css

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,54 @@ h1 {
183183
}
184184
.modifyButtonGroup button:disabled {
185185
}
186+
187+
.modalContent {
188+
padding: 10px 10px 30px;
189+
}
190+
191+
.inputGroup {
192+
display: flex;
193+
flex-direction: column;
194+
gap: 8px;
195+
}
196+
197+
.label {
198+
font-size: 0.9rem;
199+
font-weight: 600;
200+
color: #555;
201+
}
202+
203+
.selectInput {
204+
width: 100%;
205+
padding: 10px;
206+
font-size: 1rem;
207+
border: 1px solid #ddd;
208+
border-radius: 6px;
209+
outline: none;
210+
background-color: #fff;
211+
cursor: pointer;
212+
}
213+
214+
.selectInput:focus {
215+
border-color: #3b82f6;
216+
}
217+
218+
.button {
219+
padding: 10px 20px;
220+
font-size: 1rem;
221+
font-weight: 600;
222+
border-radius: 6px;
223+
cursor: pointer;
224+
border: none;
225+
}
226+
227+
@keyframes fadeIn {
228+
from {
229+
opacity: 0;
230+
transform: translateY(-10px);
231+
}
232+
to {
233+
opacity: 1;
234+
transform: translateY(0);
235+
}
236+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { useState, useEffect } from 'react';
2+
import styles from '../VerificationModal.module.css';
3+
import { useAttendance } from '../../contexts/AttendanceContext';
4+
import { getUserList } from '../../utils/attendanceManage';
5+
6+
const AddUsersModal = () => {
7+
const { sessions, selectedSessionId, handleAddUsers, closeAddUsersModal } =
8+
useAttendance();
9+
const [selectedUserId, setSelectedUserId] = useState(null);
10+
const [users, setUsers] = useState([]);
11+
12+
// ESC 키로 모달 또는 토스트를 닫는 기능
13+
useEffect(() => {
14+
// 유저 리스트 가져오기
15+
const fetchUsers = async () => {
16+
try {
17+
const userList = await getUserList();
18+
setUsers(userList);
19+
} catch (err) {
20+
console.error('사용자 목록을 불러오는 데 실패했습니다:', err);
21+
}
22+
};
23+
if (selectedSessionId) {
24+
fetchUsers();
25+
}
26+
27+
const handleKeyDown = (event) => {
28+
if (event.key === 'Escape') {
29+
closeAddUsersModal();
30+
}
31+
};
32+
document.addEventListener('keydown', handleKeyDown);
33+
return () => {
34+
document.removeEventListener('keydown', handleKeyDown);
35+
};
36+
}, [closeAddUsersModal]);
37+
38+
const handleComplete = () => {
39+
const currentSession = sessions.find(
40+
(s) => s.attendanceSessionId === selectedSessionId
41+
);
42+
43+
if (!currentSession) {
44+
alert('세션을 먼저 선택해주세요.');
45+
return;
46+
}
47+
if (!selectedUserId) {
48+
alert('추가할 인원를 1명 이상 선택해주세요.');
49+
return;
50+
}
51+
52+
handleAddUsers(selectedSessionId, selectedUserId);
53+
closeAddUsersModal();
54+
};
55+
56+
return (
57+
<div className={styles.overlay}>
58+
<div className={styles.modal}>
59+
<div className={styles.modalHeader}>
60+
<h1>세션에 유저 추가하기</h1>
61+
<button
62+
type="button"
63+
className={styles.closeButton}
64+
onClick={() => {
65+
closeAddUsersModal();
66+
}}
67+
>
68+
&times;
69+
</button>
70+
</div>
71+
72+
<div className={styles.modalContent}>
73+
<div className={styles.inputGroup}>
74+
<label htmlFor="userSelect" className={styles.label}>
75+
유저 선택
76+
</label>
77+
<select
78+
id="userSelect"
79+
className={styles.selectInput}
80+
value={selectedUserId || ''}
81+
onChange={(e) => setSelectedUserId(e.target.value)}
82+
>
83+
<option value="" disabled>
84+
------ 유저를 선택하세요 ------
85+
</option>
86+
{users &&
87+
users.map((user) => (
88+
<option key={user.userId} value={user.userId}>
89+
{user.name} ({user.email})
90+
</option>
91+
))}
92+
</select>
93+
</div>
94+
</div>
95+
96+
<div className={styles.modifyButtonGroup}>
97+
<button
98+
className={`${styles.button} ${styles.submitButton}`}
99+
onClick={handleComplete}
100+
>
101+
추가
102+
</button>
103+
</div>
104+
</div>
105+
</div>
106+
);
107+
};
108+
109+
export default AddUsersModal;

frontend/src/components/attendancemanage/AttendanceManagementCard.jsx

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
11
import styles from './AttendanceManagementCard.module.css';
22

33
import { useAttendance } from '../../contexts/AttendanceContext';
4+
import { useEffect, useState } from 'react';
5+
import { getRoundUserAttendance } from '../../utils/attendanceManage';
6+
7+
const attendanceEnglishToKorean = {
8+
PRESENT: '출석',
9+
LATE: '지각',
10+
ABSENT: '결석',
11+
EXCUSED: '공결',
12+
PENDING: '미정',
13+
};
414

515
const AttendanceManagementCard = ({ styles: commonStyles }) => {
6-
const { selectedRound, handleAttendanceChange, participants } =
7-
useAttendance();
16+
const {
17+
selectedSessionId,
18+
selectedRound,
19+
handleAttendanceChange,
20+
roundAttendanceVersion,
21+
} = useAttendance();
22+
23+
const [users, setUsers] = useState([]);
24+
25+
useEffect(() => {
26+
const fetchUsers = async () => {
27+
if (selectedSessionId && selectedRound) {
28+
const userList = await getRoundUserAttendance(selectedRound);
29+
// const sortedUsers = (userList || []).sort(
30+
// (a, b) =>
31+
// new Date(`${a.date}T${a.startTime}`) -
32+
// new Date(`${b.date}T${b.startTime}`)
33+
// );
34+
setUsers(userList);
35+
} else {
36+
setUsers([]);
37+
}
38+
};
39+
fetchUsers();
40+
}, [selectedSessionId, selectedRound, roundAttendanceVersion]);
841

942
return (
1043
<div className={styles.attendanceManagementCardContainer}>
@@ -18,7 +51,7 @@ const AttendanceManagementCard = ({ styles: commonStyles }) => {
1851
<th>이름</th>
1952
<th>상태</th>
2053
<th>변경</th>
21-
<th>횟수</th>
54+
<th>이메일</th>
2255
</tr>
2356
</thead>
2457
<tbody>
@@ -28,27 +61,28 @@ const AttendanceManagementCard = ({ styles: commonStyles }) => {
2861
회차를 선택해주세요.
2962
</td>
3063
</tr>
31-
) : participants.length > 0 ? (
32-
participants.map((participant) => (
33-
<tr key={participant.memberId}>
34-
<td>{participant.name}</td>
35-
<td>{participant.attendance}</td>
64+
) : users.length > 0 ? (
65+
users.map((user) => (
66+
<tr key={user.userId}>
67+
<td>{user.userName}</td>
68+
<td>{attendanceEnglishToKorean[user.attendanceStatus]}</td>
3669
<td>
3770
<select
3871
className={styles.attendanceSelect}
39-
value={participant.attendance}
72+
value={user.attendanceStatus}
4073
onChange={(e) =>
41-
handleAttendanceChange(
42-
participant.memberId,
43-
e.target.value
44-
)
74+
handleAttendanceChange(user.userId, e.target.value)
4575
}
4676
>
47-
<option value="출석">출석</option>
48-
<option value="결석">결석</option>
77+
{/* 출석(PRESENT), 지각(LATE), 결석(ABSENT), 공결(EXCUSED) */}
78+
<option value="PRESENT">출석</option>
79+
<option value="ABSENT">결석</option>
80+
<option value="LATE">지각</option>
81+
<option value="EXCUSED">공결</option>
82+
<option value="PENDING">미정</option>
4983
</select>
5084
</td>
51-
<td>-</td>
85+
<td>{user.email}</td>
5286
</tr>
5387
))
5488
) : (

frontend/src/components/attendancemanage/AttendanceManagementCard.module.css

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
.attendanceManagementCardContainer {
22
border-radius: 12px;
3-
min-width: 300px;
4-
max-width: 335px;
3+
width: 100%;
54
background: #fafbfc;
6-
padding: 32px 20px;
5+
padding: 26px 40px;
6+
border: 1px solid #c9bebe;
77
}
88

99
.tableGroup {
@@ -26,15 +26,30 @@
2626
border-radius: 4px;
2727
border: 0.8px solid #afafaf;
2828
}
29-
.table thead th,
29+
.table thead th {
30+
text-align: left;
31+
color: var(--text);
32+
padding: 8px 7px;
33+
border-bottom: 1px solid var(--line);
34+
white-space: nowrap;
35+
font-family: Inter;
36+
font-size: 20px;
37+
font-style: normal;
38+
font-weight: 400;
39+
line-height: 100%;
40+
41+
position: sticky;
42+
top: 0;
43+
background-color: #fafbfc;
44+
}
3045
.table tbody td {
3146
text-align: left;
3247
color: var(--text);
3348
padding: 8px 7px;
3449
border-bottom: 1px solid var(--line);
3550
white-space: nowrap;
3651
font-family: Inter;
37-
font-size: 16px;
52+
font-size: 18px;
3853
font-style: normal;
3954
font-weight: 400;
4055
line-height: 100%;
@@ -47,4 +62,4 @@
4762
text-align: center;
4863
padding: 20px;
4964
color: #888;
50-
}
65+
}

frontend/src/components/attendancemanage/RoundDayPicker.jsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import styles from '../VerificationModal.module.css';
33

44
import { DayPicker } from 'react-day-picker';
55
import 'react-day-picker/style.css';
6-
import { v4 as uuid } from 'uuid';
76
import { useAttendance } from '../../contexts/AttendanceContext';
87

98
const RoundDayPicker = () => {
@@ -29,7 +28,9 @@ const RoundDayPicker = () => {
2928
}, [closeAddRoundsModal]);
3029

3130
const handleComplete = () => {
32-
const currentSession = sessions.find((s) => s.id === selectedSessionId);
31+
const currentSession = sessions.find(
32+
(s) => s.attendanceSessionId === selectedSessionId
33+
);
3334

3435
if (!currentSession) {
3536
alert('세션을 먼저 선택해주세요.');
@@ -46,12 +47,12 @@ const RoundDayPicker = () => {
4647
const dateString = dateWithoutOffset.toISOString().split('T')[0];
4748

4849
return {
49-
id: `round-${uuid()}`,
50-
date: dateString,
50+
// id: `round-${uuid()}`,
51+
roundDate: dateString,
5152
startTime: currentSession.defaultStartTime,
5253
availableMinutes: currentSession.defaultAvailableMinutes,
53-
status: 'opened',
54-
participants: [],
54+
// status: 'opened',
55+
// participants: [],
5556
};
5657
});
5758

frontend/src/components/attendancemanage/RoundModifyModal.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ConfirmationToast from './ConfirmationToast';
66
const RoundModifyModal = ({
77
styles: commonStyles,
88
onClose,
9+
sessionId,
910
round,
1011
onSave,
1112
onDelete,
@@ -80,10 +81,12 @@ const RoundModifyModal = ({
8081
if (!isFormValid(hour, minute, second, availableMinute)) return;
8182

8283
// 상위 컴포넌트로 업데이트된 회차 데이터 전달
83-
onSave({
84-
id: round.id,
84+
onSave(round.id, {
85+
// roundId: round.id,
86+
sessionId: sessionId,
87+
roundDate: round.date,
8588
startTime: `${hh.padStart(2, '0')}:${mm.padStart(2, '0')}:${ss.padStart(2, '0')}`,
86-
availableMinutes: availableMinute,
89+
allowedMinutes: availableMinute,
8790
});
8891

8992
onClose();
@@ -127,7 +130,7 @@ const RoundModifyModal = ({
127130
</div>
128131

129132
<div className={styles.form}>
130-
<div className={commonStyles.inputGroup}>
133+
<div className={commonStyles.modalInputGroup}>
131134
<label htmlFor="sessionStartTime" className={commonStyles.label}>
132135
출석 시작 시간
133136
</label>

0 commit comments

Comments
 (0)