[FE] [FEAT] : 출석하기(담당자) 페이지 디자인 수정 및 기능 api 연결#165
Conversation
Walkthrough출석 관리 시스템에 사용자 추가 기능, 라운드/세션 데이터 비동기 관리, API 통합을 추가합니다. 컨텍스트, 컴포넌트, 유틸리티, 스타일 전반에 걸쳐 구조적 개선을 포함합니다. Changes
Sequence DiagramsequenceDiagram
actor User
participant SessionManageCard as SessionManagement<br/>Card
participant Modal as AddUsersModal
participant Context as Attendance<br/>Context
participant API as API Utils
participant Server as Backend
User->>SessionManageCard: 세션 선택 및 사용자 추가 버튼 클릭
SessionManageCard->>Context: openAddUsersModal()
Context->>Modal: isAddUsersModalOpen = true
activate Modal
Modal->>API: getUserList()
API->>Server: GET /api/attendance/users
Server-->>API: users[]
API-->>Modal: users[]
Modal->>User: 사용자 목록 표시
User->>Modal: 사용자 선택 및 추가 버튼 클릭
Modal->>Modal: 선택 검증
alt 유효
Modal->>Context: handleAddUsers(sessionId, userId)
Context->>API: addUser(sessionId, userId)
API->>Server: POST /api/attendance/sessions/{id}/users
Server-->>API: success
Context->>Context: roundAttendanceVersion++
Context->>Modal: closeAddUsersModal()
deactivate Modal
Modal-->>User: 모달 종료
else 유효하지 않음
Modal->>User: 알림 표시
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
특히 주의 깊게 검토할 영역:
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)
165-170: colSpan 값 불일치테이블 헤더에 5개의 컬럼(일자, 시간, 가능(분), 회차, 메뉴)이 있지만, 빈 데이터 행의
colSpan이"4"로 설정되어 있습니다.) : ( <tr> - <td colSpan="4" className={styles.noData}> + <td colSpan="5" className={styles.noData}> 회차 정보가 없습니다. </td> </tr>
🧹 Nitpick comments (12)
frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)
31-33: 회차 데이터 필드명(availableMinutes / allowedMinutes, date / roundDate) 정합성 확인 필요
handleComplete에서 생성하는newRounds가{ roundDate, startTime, availableMinutes }형태인데, 다른 컴포넌트/유틸(예:RoundModifyModal,attendanceManage.js) 요약에서는roundDate+allowedMinutes조합을 사용한다고 되어 있습니다. 필드명이 혼재되어 있으면 회차 수정/저장 시 날짜나 출석 가능 시간 정보가 누락되거나 무시될 여지가 있습니다.AttendanceContext/
attendanceManage.js에서 사용하는 실제 round 객체 스키마를 한 번 더 확인하시고,
roundDatevsdate,availableMinutesvsallowedMinutes중 하나로 전체를 통일해 두는 편이 안전해 보입니다.
Also applies to: 44-56
frontend/src/components/attendancemanage/SessionModifyModal.jsx (1)
88-96: 세션 payload의 allowedMinutes 타입 정리 및 필드 스키마 확인 권장
handleModifyClick에서:
availableMinute = parseInt(defaultAvailableMinutes, 10);로 숫자 변환을 해두셨지만,- 실제 payload에는
allowedMinutes: defaultAvailableMinutes(문자열 상태)를 그대로 넘기고 있습니다.API 쪽에서 분 단위를 숫자로 받는다면, 아래처럼
availableMinute를 사용하는 편이 더 안전해 보입니다.- defaultStartTime: `${hh.padStart(2, '0')}:${mm.padStart( - 2, - '0' - )}:${ss.padStart(2, '0')}`, - allowedMinutes: defaultAvailableMinutes, + defaultStartTime: `${hh.padStart(2, '0')}:${mm.padStart( + 2, + '0' + )}:${ss.padStart(2, '0')}`, + allowedMinutes: availableMinute,또한 세션/라운드 전반에서
defaultAvailableMinutesvsallowedMinutes네이밍이 혼재되어 있으니, RoundModifyModal·RoundDayPicker·attendanceManage.js와 함께 한 번 더 스키마를 맞춰 두는 것을 추천드립니다.frontend/src/pages/AttendanceManage.module.css (1)
14-21: cardLayout 그리드 설정과 grid-area 사용 의도 한 번 확인 필요
.cardLayout에서 desktop 구간의grid-template-columns/grid-template-areas가 제거되어 현재는 auto-placement 그리드(display: grid만 존재) 상태입니다. 이 경우:
.settings,.session,.roster에 지정된grid-area속성은 desktop 레이아웃에서는 더 이상 의미가 없고,- 요소들은 DOM 순서대로 한 열에 쌓이는 형태에 가까워집니다(미디어쿼리 이하에서는 여전히 grid-areas를 씀).
의도적으로 모든 해상도에서 1열 스택 레이아웃을 쓰려는 것인지, 아니면 desktop 에서는 예전처럼 2열 배치를 유지하려는 것인지 한 번만 확인해 보시면 좋겠습니다. 필요하다면 desktop 구간에 맞는
grid-template-columns또는 flex 레이아웃으로 정리해도 좋을 것 같습니다.Also applies to: 37-45
frontend/src/components/attendancemanage/SessionManagementCard.module.css (1)
72-91: 열 width 합계(105%)로 인한 미묘한 오버플로 가능성현재 열 폭이
- 1열: 25%
- 2열: 25%
- 3열: 25%
- 4열: 20%
- 5열: 10%
로 합계가 105%입니다. 브라우저가 자동으로 비율을 조정하긴 하지만, 경우에 따라 약간의 수평 스크롤이나 예상과 다른 폭 배분이 나올 수 있습니다.
실제 의도에 맞게 예를 들어 3열/4열 비율을 조금 줄여서 총합을 100% 내로 맞추는 것도 고려해 보시면 좋겠습니다.
frontend/src/components/attendancemanage/AddUsersModal.jsx (1)
12-37: useEffect 의존성 배열에 selectedSessionId 추가 권장
useEffect안에서:
selectedSessionId를 참조해fetchUsers()호출 여부를 결정하고,- ESC 키 핸들러에서
closeAddUsersModal을 사용하고 있는데,의존성 배열에는
closeAddUsersModal만 들어가 있습니다. 현재 용도(모달이 열릴 때 한 번 유저 목록을 가져오는 형태)에서는 큰 문제는 없지만, React 훅 규칙/ESLint 관점에서는selectedSessionId도 포함하는 편이 더 안전합니다.예시:
- }, [closeAddUsersModal]); + }, [closeAddUsersModal, selectedSessionId]);유저 목록이 세션과 무관한 전체 리스트라 하더라도, 의존성 배열을 실제로 사용하는 값과 맞춰 두면 훅 관련 경고를 예방할 수 있습니다.
frontend/src/components/attendancemanage/SessionManagementCard.jsx (2)
81-84: 아이콘 재사용 검토"세션에 사용자 추가" 버튼에
calendar-icon.svg가 사용되고 있습니다. 사용자 추가 기능에 더 적합한 아이콘(예: 사람 추가 아이콘)을 사용하는 것이 UX 측면에서 좋을 수 있습니다.
113-119: 일관성 없는 알림 방식다른 버튼에서는
toast.error()를 사용하지만, 세션 수정 버튼에서는alert()를 사용하고 있습니다. 일관성을 위해toast를 사용하는 것이 좋습니다.onClick={() => { if (currentSession) { setEditingSession(currentSession); openSessionModifyModal(); } else { - alert('세션을 선택해주세요.'); + toast.error('세션을 선택해주세요.'); } }}frontend/src/pages/AttendanceManage.jsx (1)
48-70: 주석 처리된 디버그 코드 제거주석 처리된 디버그 버튼들은 프로덕션 코드에서 제거되어야 합니다.
frontend/src/utils/attendanceManage.js (1)
3-15: 주석 처리된 코드 제거 권장사용하지 않는
getFormattedCurrentTime함수가 주석으로 남아있습니다. 필요하지 않다면 제거하거나, 향후 사용 계획이 있다면 TODO 주석을 추가하세요.frontend/src/contexts/AttendanceContext.jsx (3)
1-7: 사용되지 않는 import
useCallback이 import되었지만 파일 내에서 사용되지 않습니다.import { createContext, - useCallback, useContext, useEffect, useState, } from 'react';
24-66: 주석 처리된 목 데이터 제거API 연동이 완료되었으므로 주석 처리된 mock 데이터는 제거해도 됩니다. 참고용으로 필요하다면 별도 문서나 테스트 파일로 이동하는 것이 좋습니다.
354-359: 에러 메시지 추가 권장
useAttendance훅에서 컨텍스트가 없을 때 throw되는 에러에 메시지가 없어 디버깅이 어렵습니다.export const useAttendance = () => { const context = useContext(AttendanceContext); if (context === null) { - throw new Error(); + throw new Error('useAttendance must be used within an AttendanceProvider'); } return context; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
frontend/src/assets/calendar-icon.svgis excluded by!**/*.svg
📒 Files selected for processing (14)
frontend/src/components/VerificationModal.module.css(1 hunks)frontend/src/components/attendancemanage/AddUsersModal.jsx(1 hunks)frontend/src/components/attendancemanage/AttendanceManagementCard.jsx(3 hunks)frontend/src/components/attendancemanage/AttendanceManagementCard.module.css(3 hunks)frontend/src/components/attendancemanage/RoundDayPicker.jsx(2 hunks)frontend/src/components/attendancemanage/RoundModifyModal.jsx(3 hunks)frontend/src/components/attendancemanage/SessionManagementCard.jsx(5 hunks)frontend/src/components/attendancemanage/SessionManagementCard.module.css(4 hunks)frontend/src/components/attendancemanage/SessionModifyModal.jsx(2 hunks)frontend/src/components/attendancemanage/SessionSettingCard.module.css(1 hunks)frontend/src/contexts/AttendanceContext.jsx(3 hunks)frontend/src/pages/AttendanceManage.jsx(4 hunks)frontend/src/pages/AttendanceManage.module.css(5 hunks)frontend/src/utils/attendanceManage.js(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
frontend/src/components/attendancemanage/AddUsersModal.jsx (5)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (2)
useAttendance(10-23)currentSession(27-29)frontend/src/pages/AttendanceManage.jsx (1)
useAttendance(29-44)frontend/src/components/attendancemanage/RoundDayPicker.jsx (2)
useAttendance(9-10)handleComplete(30-62)frontend/src/contexts/AttendanceContext.jsx (4)
useAttendance(354-360)useAttendance(354-360)selectedSessionId(70-70)sessions(69-69)frontend/src/utils/attendanceManage.js (2)
getUserList(195-203)getUserList(195-203)
frontend/src/components/attendancemanage/SessionModifyModal.jsx (2)
frontend/src/components/attendancemanage/RoundModifyModal.jsx (4)
hh(27-27)mm(28-28)ss(29-29)handleDeleteClick(95-115)frontend/src/components/attendancemanage/SessionSettingCard.jsx (3)
hh(9-9)mm(10-10)ss(11-11)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (7)
frontend/src/pages/AttendanceManage.jsx (1)
useAttendance(29-44)frontend/src/components/attendancemanage/AddUsersModal.jsx (1)
useAttendance(7-8)frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (1)
useAttendance(16-21)frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)
useAttendance(9-10)frontend/src/contexts/AttendanceContext.jsx (7)
useAttendance(354-360)useAttendance(354-360)sessions(69-69)selectedSessionId(70-70)roundsVersion(80-80)openAddRoundsModal(180-180)openAddUsersModal(177-177)frontend/src/components/attendancemanage/SessionSettingCard.jsx (1)
useAttendance(6-6)frontend/src/utils/attendanceManage.js (2)
getRounds(51-59)getRounds(51-59)
frontend/src/pages/AttendanceManage.jsx (5)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)
useAttendance(10-23)frontend/src/components/attendancemanage/AddUsersModal.jsx (2)
useAttendance(7-8)AddUsersModal(6-107)frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)
useAttendance(9-10)frontend/src/contexts/AttendanceContext.jsx (4)
useAttendance(354-360)useAttendance(354-360)selectedSessionId(70-70)isAddUsersModalOpen(78-78)frontend/src/components/attendancemanage/SessionSettingCard.jsx (1)
useAttendance(6-6)
frontend/src/components/attendancemanage/RoundModifyModal.jsx (2)
frontend/src/components/attendancemanage/SessionModifyModal.jsx (3)
hh(26-26)mm(27-27)ss(28-28)frontend/src/components/attendancemanage/SessionSettingCard.jsx (3)
hh(9-9)mm(10-10)ss(11-11)
frontend/src/utils/attendanceManage.js (1)
frontend/src/contexts/AttendanceContext.jsx (1)
sessionData(25-66)
🔇 Additional comments (14)
frontend/src/components/attendancemanage/AttendanceManagementCard.module.css (1)
1-7: 테이블 컨테이너/헤더 스타일 변경 방향 적절해 보입니다
width: 100%, padding/border 추가, thead sticky 처리와 폰트 사이즈 상향(20px/18px)으로 가독성이 좋아질 것 같습니다. 스크롤 영역(.tableGroup)과도 잘 맞는 구조라 기능적인 문제는 없어 보입니다.Also applies to: 29-44, 52-56
frontend/src/components/attendancemanage/RoundModifyModal.jsx (2)
9-13: sessionId 전달 및 modalInputGroup 사용은 의도에 잘 맞습니다
sessionId를 별도 prop으로 받아 payload에 포함시키는 구조는 세션 단위 회차 관리에 적합해 보입니다.- 시간 입력 섹션을
commonStyles.modalInputGroup으로 변경해 모달 전용 인풋 스타일(AttendanceManage.module.css의.modalInputGroup)과 일관되게 맞춘 점도 좋습니다.기능/레이아웃 측면에서 특별한 문제는 없어 보입니다.
Also applies to: 133-161
30-32: Field naming consistency could be clearer, but the current implementation is functionally correctThe initialization of
availableTimeMmwithround.availableMinutesis correct—that's the actual field name in the API response. However, there is a legitimate concern about field name mapping consistency:
- Backend entity uses
roundDate, but the API response maps this todate(seeAttendanceRoundResponse.fromEntity())- Frontend receives
round.date, but sends it back to backend asroundDatein the payload- Similarly,
allowedMinutes(backend) is mapped toavailableMinutes(frontend response)The code currently works because the payload structure (
roundDate,startTime,allowedMinutes) matches what the backend API expects, andround.availableMinutescorrectly accesses the frontend data. However, the inconsistency between response field names (date,availableMinutes) and request field names (roundDate,allowedMinutes) creates unnecessary confusion.To improve clarity without breaking functionality, consider either:
- Documenting this mapping in comments, or
- Creating a normalization layer in the frontend to use consistent field names throughout
The suggested fix in the original comment (using
round.allowedMinutes) is incorrect—that field doesn't exist in the API response; the correct field isround.availableMinutes.frontend/src/components/attendancemanage/SessionModifyModal.jsx (1)
101-107: attendanceSessionId 기반 삭제 및 모달 인풋 레이아웃 변경은 일관성 있게 보입니다
- 삭제 시
onDelete(session.attendanceSessionId)로 변경한 것은 컨텍스트/유틸들이attendanceSessionId를 키로 사용하는 흐름과 잘 맞습니다.- 시간 입력 영역을
commonStyles.modalInputGroup으로 분리해 모달 전용 인풋 스타일을 재사용하는 구조도 좋아 보입니다.해당 부분은 별다른 동작 문제 없이 잘 정리된 것 같습니다.
Also applies to: 139-167
frontend/src/components/attendancemanage/SessionSettingCard.module.css (1)
13-18: 세션 설정 카드 레이아웃/간격 조정 잘 되어 있습니다컨테이너를
width: 100%+ border로 통일하고,.form을 flex로 바꾸며 time/available input 그룹을width: auto+ gap 조정한 부분이 전체 AttendanceManage 카드 스타일과 잘 맞습니다. 기능적으로도 문제 없어 보입니다.Also applies to: 20-23, 27-30, 37-40
frontend/src/pages/AttendanceManage.module.css (2)
73-88: 아이콘/헤더 타이포그래피 조정이 UI 일관성에 도움이 됩니다
- 아이콘 버튼 이미지를 30×30으로 키운 점,
.iconGroup+.text를 도입해 아이콘 + 보조 텍스트 패턴을 만든 점,- 헤더 타이틀 폰트를 25px로 상향한 점
등이 전체 AttendanceManage 카드들의 시각적 위계를 맞추는 데 도움이 됩니다. 동작상의 문제는 없어 보입니다.
Also applies to: 90-97
104-107: 폼 input / 모달 input 스타일 공통화 잘 되어 있습니다
.inputGroup input과.modalInputGroup input을 분리해:
- 일반 폼 인풋은
width: 300px,font-size: 16px,- 모달 인풋은
width: 116px,font-size: 14px,으로 정의한 구조가 직관적이고, Round/Session 모달에서 재사용하기에도 좋아 보입니다. 포커스 스타일도 일관되게 적용되어 있어 UX 측면에서도 무난합니다.
Also applies to: 113-124, 139-160
frontend/src/components/attendancemanage/SessionManagementCard.module.css (1)
3-8: 세션 관리 테이블 가독성 및 헤더 스티키 처리 적절
- 카드 컨테이너에 padding/border/width 100%를 준 부분,
sessionSelect의 폭(30%)과 폰트(18px) 상향,- thead/td 폰트 20px/18px + sticky header 적용
등으로 테이블 가독성과 사용성이 전반적으로 좋아졌습니다. 동작 측면에서 특별한 문제는 없어 보입니다.
Also applies to: 15-26, 42-57, 61-66
frontend/src/components/attendancemanage/AddUsersModal.jsx (1)
56-105: AddUsersModal 전반 동작 흐름은 자연스럽습니다
- 세션 선택 여부 확인 후 없을 때 얼럿으로 안내하고,
getUserList로 가져온 유저 리스트를 select로 표시해 선택,- 완료 시
handleAddUsers(selectedSessionId, selectedUserId)호출 후 모달을 닫는 흐름이 전체 AttendanceContext/페이지 구조와 잘 맞습니다. ESC 키로 모달을 닫는 UX도 기존 모달 패턴과 일관적입니다.
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (2)
7-13: LGTM!출석 상태 매핑이 select 옵션과 일관되게 정의되어 있습니다.
64-87: LGTM!사용자 목록 렌더링 로직과 출석 상태 변경 핸들러 연결이 올바르게 구현되어 있습니다.
frontend/src/pages/AttendanceManage.jsx (1)
82-102: LGTM!모달 컴포넌트들의 조건부 렌더링과
sessionIdprop 전달이 올바르게 구현되어 있습니다.frontend/src/utils/attendanceManage.js (1)
17-26: LGTM!API wrapper 함수들이 일관된 패턴으로 구현되어 있습니다. 에러 로깅 후 재throw하여 호출자가 처리할 수 있도록 되어있습니다.
frontend/src/contexts/AttendanceContext.jsx (1)
85-96: useEffect 의존성 배열 검토
setSessions가 dependency array에 포함되어 있습니다.useImmer의 setter는 안정적인 참조를 가지므로 문제가 없지만, ESLint 경고를 피하기 위한 것이라면 주석으로 명시하는 것이 좋습니다.
| useEffect(() => { | ||
| const fetchUsers = async () => { | ||
| if (selectedSessionId && selectedRound) { | ||
| const userList = await getRoundUserAttendance(selectedRound); | ||
| // const sortedUsers = (userList || []).sort( | ||
| // (a, b) => | ||
| // new Date(`${a.date}T${a.startTime}`) - | ||
| // new Date(`${b.date}T${b.startTime}`) | ||
| // ); | ||
| setUsers(userList); | ||
| } else { | ||
| setUsers([]); | ||
| } | ||
| }; | ||
| fetchUsers(); | ||
| }, [selectedSessionId, selectedRound, roundAttendanceVersion]); |
There was a problem hiding this comment.
useEffect 내 에러 처리 누락
getRoundUserAttendance 호출이 실패할 경우 에러가 처리되지 않아 사용자에게 피드백이 없고 콘솔에 unhandled rejection이 발생할 수 있습니다.
useEffect(() => {
const fetchUsers = async () => {
if (selectedSessionId && selectedRound) {
- const userList = await getRoundUserAttendance(selectedRound);
- setUsers(userList);
+ try {
+ const userList = await getRoundUserAttendance(selectedRound);
+ setUsers(userList || []);
+ } catch (error) {
+ console.error('출석 정보를 불러오는 데 실패했습니다:', error);
+ setUsers([]);
+ }
} else {
setUsers([]);
}
};
fetchUsers();
}, [selectedSessionId, selectedRound, roundAttendanceVersion]);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| const fetchUsers = async () => { | |
| if (selectedSessionId && selectedRound) { | |
| const userList = await getRoundUserAttendance(selectedRound); | |
| // const sortedUsers = (userList || []).sort( | |
| // (a, b) => | |
| // new Date(`${a.date}T${a.startTime}`) - | |
| // new Date(`${b.date}T${b.startTime}`) | |
| // ); | |
| setUsers(userList); | |
| } else { | |
| setUsers([]); | |
| } | |
| }; | |
| fetchUsers(); | |
| }, [selectedSessionId, selectedRound, roundAttendanceVersion]); | |
| useEffect(() => { | |
| const fetchUsers = async () => { | |
| if (selectedSessionId && selectedRound) { | |
| try { | |
| const userList = await getRoundUserAttendance(selectedRound); | |
| // const sortedUsers = (userList || []).sort( | |
| // (a, b) => | |
| // new Date(`${a.date}T${a.startTime}`) - | |
| // new Date(`${b.date}T${b.startTime}`) | |
| // ); | |
| setUsers(userList || []); | |
| } catch (error) { | |
| console.error('출석 정보를 불러오는 데 실패했습니다:', error); | |
| setUsers([]); | |
| } | |
| } else { | |
| setUsers([]); | |
| } | |
| }; | |
| fetchUsers(); | |
| }, [selectedSessionId, selectedRound, roundAttendanceVersion]); |
🤖 Prompt for AI Agents
In frontend/src/components/attendancemanage/AttendanceManagementCard.jsx around
lines 25 to 40, the async getRoundUserAttendance call inside useEffect lacks
error handling; wrap the fetchUsers logic in a try/catch, call
getRoundUserAttendance inside try, setUsers with the result on success, and in
catch log the error (console.error) and setUsers([]) (or set an existing error
state / show a user-facing notification) so rejections are handled and the UI
gets sensible feedback.
There was a problem hiding this comment.
@gxuoo 동의해 주셔서 감사합니다! 에러 처리를 추가하면 API 호출 실패 시 사용자 경험이 개선되고 디버깅도 훨씬 쉬워질 것입니다.
@NaturalSoda4552 님, 위의 제안된 코드를 참고하여 try/catch 블록을 추가해 주시면 감사하겠습니다! 🙏
(ᵔᴥᵔ)ʃ♡
|
|
||
| .modalContent { | ||
| padding: 10px 10px 30px; | ||
| } | ||
|
|
||
| .inputGroup { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 8px; | ||
| } | ||
|
|
||
| .label { | ||
| font-size: 0.9rem; | ||
| font-weight: 600; | ||
| color: #555; | ||
| } | ||
|
|
||
| .selectInput { | ||
| width: 100%; | ||
| padding: 10px; | ||
| font-size: 1rem; | ||
| border: 1px solid #ddd; | ||
| border-radius: 6px; | ||
| outline: none; | ||
| background-color: #fff; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .selectInput:focus { | ||
| border-color: #3b82f6; | ||
| } | ||
|
|
||
| .button { | ||
| padding: 10px 20px; | ||
| font-size: 1rem; | ||
| font-weight: 600; | ||
| border-radius: 6px; | ||
| cursor: pointer; | ||
| border: none; | ||
| } | ||
|
|
||
| @keyframes fadeIn { | ||
| from { | ||
| opacity: 0; | ||
| transform: translateY(-10px); | ||
| } | ||
| to { | ||
| opacity: 1; | ||
| transform: translateY(0); | ||
| } | ||
| } |
There was a problem hiding this comment.
CSS 규칙 중복 정의 - 스타일 충돌 위험
.inputGroup, .label, .button 클래스가 파일에서 여러 번 정의되어 있습니다(라인 57-59, 67-70, 100-121 그리고 다시 191-225). CSS 캐스케이딩으로 인해 나중의 정의가 이전 정의를 덮어쓰게 되므로, sendButton, submitButton, resetPasswordButton 등 기존 버튼 변수(라인 123-142)의 스타일이 예상치 못하게 변경될 수 있습니다.
특히 새로운 .button 정의(218-225)는 기존 정의에서 제공하는 중요한 속성들(display: inline-flex, width, height, font-family, line-height, letter-spacing 등)을 잃어버립니다.
제안: 중복을 제거하고 필요한 스타일을 기존 정의에 통합하거나, 새로운 선택자를 사용하여 스타일을 명확히 분리하세요.
다음은 권장 접근 방식입니다:
옵션 1: 기존 규칙 통합
.inputGroup {
margin-bottom: 21px;
}
.inputGroup label {
display: block;
margin-bottom: 12px;
font-weight: 500;
color: #555;
}
-.label {
+/* 기존 .label을 제거하고 필요시 새 규칙과 병합 */
+.label {
display: block;
- margin-bottom: 0.5rem;
+ margin-bottom: 0.5rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #555;
}옵션 2: 새로운 선택자 사용
-.button {
- display: inline-flex;
- ...
+.buttonPrimary {
+ display: inline-flex;
+ ...
}
+
+.button {
+ padding: 10px 20px;
+ font-size: 1rem;
+ font-weight: 600;
+ border-radius: 6px;
+ cursor: pointer;
+ border: none;
+}Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
@gxuoo, 네, CSS 클래스 중복 문제를 해결해드리겠습니다. 새로 추가된 스타일들이 기존 스타일과 충돌하지 않도록 더 구체적인 클래스명을 사용하는 것이 좋겠습니다.
다음과 같이 수정하시면 됩니다:
.modalContent {
padding: 10px 10px 30px;
}
.addUserInputGroup {
display: flex;
flex-direction: column;
gap: 8px;
}
.addUserLabel {
font-size: 0.9rem;
font-weight: 600;
color: #555;
}
.selectInput {
width: 100%;
padding: 10px;
font-size: 1rem;
border: 1px solid #ddd;
border-radius: 6px;
outline: none;
background-color: #fff;
cursor: pointer;
}
.selectInput:focus {
border-color: #3b82f6;
}
.addUserButton {
padding: 10px 20px;
font-size: 1rem;
font-weight: 600;
border-radius: 6px;
cursor: pointer;
border: none;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}변경된 클래스명:
.inputGroup→.addUserInputGroup.label→.addUserLabel.button→.addUserButton
이렇게 수정하신 후, AddUsersModal.jsx 파일에서도 해당 클래스명을 동일하게 업데이트해주셔야 합니다:
styles.inputGroup→styles.addUserInputGroupstyles.label→styles.addUserLabelstyles.button→styles.addUserButton
이렇게 하면 기존 버튼 스타일(.sendButton, .submitButton, .resetPasswordButton 등)과 충돌 없이 새로운 모달의 스타일을 독립적으로 관리할 수 있습니다.
| const handleDeleteSession = async (sessionId) => { | ||
| // setSessions((draft) => { | ||
| // const sessionIndex = draft.findIndex((session) => { | ||
| // return session.id === sessionId; | ||
| // }); | ||
| // if (sessionIndex !== -1) { | ||
| // draft.splice(sessionIndex, 1); | ||
| // } | ||
| // }); | ||
|
|
||
| // 세션 삭제 시 먼저 해당 세션의 회차들 삭제 | ||
| const roundsToDelete = await getRounds(sessionId); | ||
| if (roundsToDelete && roundsToDelete.length > 0) { | ||
| for (const round of roundsToDelete) { | ||
| await deleteRound(round.id); | ||
| } | ||
| }); | ||
| } | ||
| // 세션 삭제 | ||
| await deleteSession(sessionId); | ||
|
|
||
| const updatedSessions = await getAttendanceSessions(); | ||
| setSessions(updatedSessions || []); | ||
|
|
||
| // 세션 선택 초기화 | ||
| setSelectedSessionId(null); | ||
| setSelectedRound(null); | ||
| }; |
There was a problem hiding this comment.
handleDeleteSession에 에러 처리 누락
handleDeleteSession 함수에서 API 호출들이 try/catch 없이 실행됩니다. 삭제 중 오류 발생 시 사용자에게 피드백이 없고, 부분 삭제 상태로 남을 수 있습니다.
const handleDeleteSession = async (sessionId) => {
- // 세션 삭제 시 먼저 해당 세션의 회차들 삭제
- const roundsToDelete = await getRounds(sessionId);
- if (roundsToDelete && roundsToDelete.length > 0) {
- for (const round of roundsToDelete) {
- await deleteRound(round.id);
+ try {
+ // 세션 삭제 시 먼저 해당 세션의 회차들 삭제
+ const roundsToDelete = await getRounds(sessionId);
+ if (roundsToDelete && roundsToDelete.length > 0) {
+ for (const round of roundsToDelete) {
+ await deleteRound(round.id);
+ }
}
- }
- // 세션 삭제
- await deleteSession(sessionId);
+ // 세션 삭제
+ await deleteSession(sessionId);
- const updatedSessions = await getAttendanceSessions();
- setSessions(updatedSessions || []);
+ const updatedSessions = await getAttendanceSessions();
+ setSessions(updatedSessions || []);
+ } catch (error) {
+ console.error('세션 삭제에 실패했습니다:', error);
+ return;
+ }
// 세션 선택 초기화
setSelectedSessionId(null);🤖 Prompt for AI Agents
In frontend/src/contexts/AttendanceContext.jsx around lines 245 to 271, wrap the
async delete flow in a try/catch/finally: perform rounds deletion and session
deletion inside the try, stop and throw/handle if any deleteRound/deleteSession
call fails (to avoid partial deletes), display an error to the user via the
app's toast/notification API in the catch, and in finally refresh sessions with
getAttendanceSessions() (or only refresh on success if you prefer) and clear
selection only on successful deletion; also consider adding a loading state
around the operation so the UI reflects the in-progress state.
There was a problem hiding this comment.
@gxuoo 네, 에러 처리를 추가하겠습니다! 다음과 같이 수정해주세요:
const handleDeleteSession = async (sessionId) => {
try {
// 세션 삭제 시 먼저 해당 세션의 회차들 삭제
const roundsToDelete = await getRounds(sessionId);
if (roundsToDelete && roundsToDelete.length > 0) {
for (const round of roundsToDelete) {
await deleteRound(round.id);
}
}
// 세션 삭제
await deleteSession(sessionId);
const updatedSessions = await getAttendanceSessions();
setSessions(updatedSessions || []);
// 세션 선택 초기화 (성공 시에만)
setSelectedSessionId(null);
setSelectedRound(null);
} catch (error) {
console.error('세션 삭제에 실패했습니다. ', error);
// 선택적: 사용자에게 에러 알림 표시
// toast.error('세션 삭제에 실패했습니다.');
}
};주요 변경사항:
- 전체 삭제 로직을
try/catch로 감싸서 에러 발생 시 처리 - 세션 선택 초기화를
try블록 안으로 이동하여 성공 시에만 실행 - 에러 발생 시 콘솔에 로그 출력
- 필요시
toast.error()를 사용하여 사용자에게 에러 메시지 표시 가능
이렇게 하면 삭제 중 오류가 발생해도 부분 삭제 상태를 방지하고 사용자에게 적절한 피드백을 제공할 수 있습니다!
| import { | ||
| getAttendanceSessions, | ||
| addUser, | ||
| getUsers, | ||
| getAttendance, | ||
| getSessionAttendance, | ||
| getRounds, | ||
| changeUserAttendance, | ||
| getRoundUserAttendance, | ||
| getUserList, | ||
| } from '../utils/attendanceManage'; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
사용되지 않는 import 제거 필요
getAttendanceSessions, addUser, getUsers, getAttendance, getSessionAttendance, getRounds, changeUserAttendance, getRoundUserAttendance, getUserList 함수들이 import되었지만 이 파일에서 사용되지 않습니다. 이들은 컨텍스트나 다른 컴포넌트에서 직접 사용되므로 여기서 제거해야 합니다.
-import {
- getAttendanceSessions,
- addUser,
- getUsers,
- getAttendance,
- getSessionAttendance,
- getRounds,
- changeUserAttendance,
- getRoundUserAttendance,
- getUserList,
-} from '../utils/attendanceManage';
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { | |
| getAttendanceSessions, | |
| addUser, | |
| getUsers, | |
| getAttendance, | |
| getSessionAttendance, | |
| getRounds, | |
| changeUserAttendance, | |
| getRoundUserAttendance, | |
| getUserList, | |
| } from '../utils/attendanceManage'; |
🤖 Prompt for AI Agents
In frontend/src/pages/AttendanceManage.jsx around lines 15 to 25, several
functions are imported but not used in this file; remove the unused imports
(getAttendanceSessions, addUser, getUsers, getAttendance, getSessionAttendance,
getRounds, changeUserAttendance, getRoundUserAttendance, getUserList) from the
import statement so only actually used utilities remain, then run the
linter/build to ensure no missing imports and update any other files that rely
on these functions to import them from their actual usage locations.
There was a problem hiding this comment.
@gxuoo, 네, 사용하지 않는 import들을 삭제하겠습니다!
아래와 같이 전체 import 블록을 제거하면 됩니다:
import {
AttendanceProvider,
useAttendance,
} from '../contexts/AttendanceContext';
import styles from './AttendanceManage.module.css';
import SessionSettingCard from '../components/attendancemanage/SessionSettingCard';
import AttendanceManagementCard from '../components/attendancemanage/AttendanceManagementCard';
import SessionManagementCard from '../components/attendancemanage/SessionManagementCard';
import RoundModifyModal from '../components/attendancemanage/RoundModifyModal';
import SessionModifyModal from '../components/attendancemanage/SessionModifyModal';
import RoundDayPicker from '../components/attendancemanage/RoundDayPicker';
import { ToastContainer } from 'react-toastify';
import AddUsersModal from '../components/attendancemanage/AddUsersModal';주석 처리된 디버그 버튼들에서 일부 함수를 사용하고 있지만, 실제 프로덕션 코드에서는 사용되지 않으므로 안전하게 제거할 수 있습니다.
| export const addRound = async (sessionId, newRound) => { | ||
| const paylaod = { | ||
| sessionId, | ||
| roundDate: newRound.roundDate, | ||
| startTime: newRound.startTime, | ||
| allowedMinutes: newRound.availableMinutes, | ||
| }; | ||
|
|
||
| try { | ||
| const res = await api.post( | ||
| `/api/attendance/sessions/${sessionId}/rounds`, | ||
| paylaod | ||
| ); | ||
| return res.data; | ||
| } catch (err) { | ||
| console.error('회차 추가 중 오류 발생', err); | ||
| throw err; | ||
| } | ||
| }; |
There was a problem hiding this comment.
변수명 오타
Line 63에 paylaod가 payload로 수정되어야 합니다.
export const addRound = async (sessionId, newRound) => {
- const paylaod = {
+ const payload = {
sessionId,
roundDate: newRound.roundDate,
startTime: newRound.startTime,
allowedMinutes: newRound.availableMinutes,
};
try {
const res = await api.post(
`/api/attendance/sessions/${sessionId}/rounds`,
- paylaod
+ payload
);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const addRound = async (sessionId, newRound) => { | |
| const paylaod = { | |
| sessionId, | |
| roundDate: newRound.roundDate, | |
| startTime: newRound.startTime, | |
| allowedMinutes: newRound.availableMinutes, | |
| }; | |
| try { | |
| const res = await api.post( | |
| `/api/attendance/sessions/${sessionId}/rounds`, | |
| paylaod | |
| ); | |
| return res.data; | |
| } catch (err) { | |
| console.error('회차 추가 중 오류 발생', err); | |
| throw err; | |
| } | |
| }; | |
| export const addRound = async (sessionId, newRound) => { | |
| const payload = { | |
| sessionId, | |
| roundDate: newRound.roundDate, | |
| startTime: newRound.startTime, | |
| allowedMinutes: newRound.availableMinutes, | |
| }; | |
| try { | |
| const res = await api.post( | |
| `/api/attendance/sessions/${sessionId}/rounds`, | |
| payload | |
| ); | |
| return res.data; | |
| } catch (err) { | |
| console.error('회차 추가 중 오류 발생', err); | |
| throw err; | |
| } | |
| }; |
🤖 Prompt for AI Agents
In frontend/src/utils/attendanceManage.js around lines 62 to 80, the local
variable name "paylaod" is misspelled; rename it to "payload" and update its
usage in the api.post call accordingly so the constructed object and the POST
call use "payload" instead of "paylaod".
There was a problem hiding this comment.
대부분 잘 만들어주셨는데, 아직 프론트를 처음 하시다 보니까 코드 작성할 때 depth가 좀 깊어지는 거 같아요. .jsx로 끝나는 컴포넌트 파일에는 화면에 렌더링 되는 것들을 보여주는 역할을 한다고 저는 생각해서 커스텀 훅을 만들거나 순수함수 (포멧팅, 유효성 검증, api호출) 같은 로직은 저는 최대한 분리하려고 해요.
아직 이에 대한 부분이 어느 곳은 이뤄져 있고 어느 곳은 안 되어 있는 것 같아서 시험 끝나고 시간이 되는대로 제가 남긴 코맨트 확인 후 수정이 끝나면 코드 분리에 대한 리팩토링을 한 번 진행해주시면 좋을 것 같습니다! 고생하셨습니다.
| ); | ||
|
|
||
| if (!currentSession) { | ||
| alert('세션을 먼저 선택해주세요.'); |
There was a problem hiding this comment.
| alert('세션을 먼저 선택해주세요.'); | |
| toast.error('세션을 먼저 선택해주세요.'); |
alert 함수 대신에 toast를 사용하는 것이 좋을 것 같습니다. line5에 아래의 import문을 추가해주세요!
import { toast } from 'react-toastify';| return; | ||
| } | ||
| if (!selectedUserId) { | ||
| alert('추가할 인원를 1명 이상 선택해주세요.'); |
| useEffect(() => { | ||
| const fetchUsers = async () => { | ||
| if (selectedSessionId && selectedRound) { | ||
| const userList = await getRoundUserAttendance(selectedRound); | ||
| // const sortedUsers = (userList || []).sort( | ||
| // (a, b) => | ||
| // new Date(`${a.date}T${a.startTime}`) - | ||
| // new Date(`${b.date}T${b.startTime}`) | ||
| // ); | ||
| setUsers(userList); | ||
| } else { | ||
| setUsers([]); | ||
| } | ||
| }; | ||
| fetchUsers(); | ||
| }, [selectedSessionId, selectedRound, roundAttendanceVersion]); |
| const AttendanceContext = createContext(null); | ||
|
|
||
| // 세션 목 데이터 | ||
| const sessionData = [ |
There was a problem hiding this comment.
사용하지 않아 보이는 거 같은데 삭제하시는 게 어떨까요??
| import { createContext, useContext, useState } from 'react'; | ||
| import { | ||
| createContext, | ||
| useCallback, |
There was a problem hiding this comment.
지금 코드로는 useCallBack을 사용하지 않는 것 같아요~~
|
|
||
| .modalContent { | ||
| padding: 10px 10px 30px; | ||
| } | ||
|
|
||
| .inputGroup { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 8px; | ||
| } | ||
|
|
||
| .label { | ||
| font-size: 0.9rem; | ||
| font-weight: 600; | ||
| color: #555; | ||
| } | ||
|
|
||
| .selectInput { | ||
| width: 100%; | ||
| padding: 10px; | ||
| font-size: 1rem; | ||
| border: 1px solid #ddd; | ||
| border-radius: 6px; | ||
| outline: none; | ||
| background-color: #fff; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .selectInput:focus { | ||
| border-color: #3b82f6; | ||
| } | ||
|
|
||
| .button { | ||
| padding: 10px 20px; | ||
| font-size: 1rem; | ||
| font-weight: 600; | ||
| border-radius: 6px; | ||
| cursor: pointer; | ||
| border: none; | ||
| } | ||
|
|
||
| @keyframes fadeIn { | ||
| from { | ||
| opacity: 0; | ||
| transform: translateY(-10px); | ||
| } | ||
| to { | ||
| opacity: 1; | ||
| transform: translateY(0); | ||
| } | ||
| } |
| const handleDeleteSession = async (sessionId) => { | ||
| // setSessions((draft) => { | ||
| // const sessionIndex = draft.findIndex((session) => { | ||
| // return session.id === sessionId; | ||
| // }); | ||
| // if (sessionIndex !== -1) { | ||
| // draft.splice(sessionIndex, 1); | ||
| // } | ||
| // }); | ||
|
|
||
| // 세션 삭제 시 먼저 해당 세션의 회차들 삭제 | ||
| const roundsToDelete = await getRounds(sessionId); | ||
| if (roundsToDelete && roundsToDelete.length > 0) { | ||
| for (const round of roundsToDelete) { | ||
| await deleteRound(round.id); | ||
| } | ||
| }); | ||
| } | ||
| // 세션 삭제 | ||
| await deleteSession(sessionId); | ||
|
|
||
| const updatedSessions = await getAttendanceSessions(); | ||
| setSessions(updatedSessions || []); | ||
|
|
||
| // 세션 선택 초기화 | ||
| setSelectedSessionId(null); | ||
| setSelectedRound(null); | ||
| }; |
| handleAddUsers, | ||
| }; | ||
|
|
||
| return ( |
There was a problem hiding this comment.
혹시 Context API를 사용하신 이유를 들을 수 있을까요??
| import { | ||
| getAttendanceSessions, | ||
| addUser, | ||
| getUsers, | ||
| getAttendance, | ||
| getSessionAttendance, | ||
| getRounds, | ||
| changeUserAttendance, | ||
| getRoundUserAttendance, | ||
| getUserList, | ||
| } from '../utils/attendanceManage'; |
| export const addRound = async (sessionId, newRound) => { | ||
| const paylaod = { | ||
| sessionId, | ||
| roundDate: newRound.roundDate, | ||
| startTime: newRound.startTime, | ||
| allowedMinutes: newRound.availableMinutes, | ||
| }; | ||
|
|
||
| try { | ||
| const res = await api.post( | ||
| `/api/attendance/sessions/${sessionId}/rounds`, | ||
| paylaod | ||
| ); | ||
| return res.data; | ||
| } catch (err) { | ||
| console.error('회차 추가 중 오류 발생', err); | ||
| throw err; | ||
| } | ||
| }; |
1) 작업한 이슈번호
2) 변경 요약 (What & Why)
3) 스크린샷/동영상 (UI 변경 시)
4) 상세 변경사항 (전부 다)
5) 참고사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.