Skip to content

[FE] [FEAT] 출석관리(관리자) 페이지 세션 정보&회차 정보 수정 기능, 세션&회차 삭제 기능, 회차 추가 기능 구현#112

Merged
DongEun02 merged 7 commits intomainfrom
attendance-manage-ui-fix
Nov 19, 2025
Merged

[FE] [FEAT] 출석관리(관리자) 페이지 세션 정보&회차 정보 수정 기능, 세션&회차 삭제 기능, 회차 추가 기능 구현#112
DongEun02 merged 7 commits intomainfrom
attendance-manage-ui-fix

Conversation

@GamjaIsMine02
Copy link
Contributor

@GamjaIsMine02 GamjaIsMine02 commented Nov 15, 2025

  1. 작업한 이슈번호

  2. 변경 요약 (What & Why)
    무엇을 변경했는지:

  • 출석관리(관리자) 페이지의 세션 정보 수정 기능, 회차 정보 수정 기능, 세션 삭제 기능, 회차 삭제 기능, 회차 추가 기능 구현

왜 변경했는지(문제/목표):

  • 미완성된 기능 구현
  1. 스크린샷/동영상 (UI 변경 시)
    전/후 비교, 반응형(모바일/데스크톱) 캡쳐
    Before:
    After:
image image image image
  1. 상세 변경사항 (전부 다)
    라우팅/페이지:
    컴포넌트: AttendanceManagementCard.jsx, RoundDayPicker.jsx, RoundModifyModal.jsx, SessionManagementCard.jsx, SessionModifyModal.jsx, SessionSettingCard.jsx, AttendanceManage.jsx
    상태관리:
    API 호출:
    스타일:
    기타:
    라이브러리:
  • useImmer: 객체 배열의 값을 직접 수정하는 것처럼 도와주며, 비교적 간결한 코드로 작성할 때 사용했습니다.
  • DayPicker: 달력 UI와 날짜 선택 기능을 제공하며, 한 번에 여러 회차를 추가할 때 사용했습니다.
  • react-toastify: 기존의 alert()대신 효과적으로 알림 팝업을 띄울 수 있게 도와주며, 회차와 세션을 삭제할 때 한 번 더 물어보고 삭제할 수 있게 사용했습니다.
  1. 참고사항
  • api 연결 예정

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 날짜 선택을 통한 일괄 회차 생성 기능 추가
    • 세션 및 회차 삭제 기능 추가
    • 세션 시작 시간 및 이용 가능 시간 수정 기능 추가
  • UI Improvements

    • 모달 레이아웃 및 버튼 크기 조정
    • 버튼 색상 및 스타일 개선

@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2025

Walkthrough

Attendance 관리가 prop 기반에서 컨텍스트 기반으로 리팩토링되었습니다. 새로운 AttendanceContext와 훅(useAttendance)을 도입하고, 다중 날짜 선택 RoundDayPicker, RoundModifyModal(이전 RoundSettingModal), SessionModifyModal, ConfirmationToast 등의 컴포넌트가 추가되었으며 CSS 및 의존성(react-day-picker, react-toastify, date-fns 등)이 갱신되었습니다.

Changes

응집 / 파일(s) 변경 요약
의존성 추가
frontend/package.json
react-day-picker, react-toastify (및 프로젝트에서 사용되는 date-fns 등 관련 날짜 라이브러리) 추가
컨텍스트 추가
frontend/src/contexts/AttendanceContext.jsx
AttendanceProvider 및 useAttendance 훅 추가: sessions, rounds, participants 중앙 상태와 CRUD/모달 제어/핸들러 제공
페이지 리팩토링
frontend/src/pages/AttendanceManage.jsx, frontend/src/pages/AttendanceManage.module.css
페이지가 AttendanceProvider로 래핑되도록 변경; 토스트 컨테이너 추가; 헤더 및 아이콘 버튼 스타일(.header, .iconButton) 추가
컴포넌트 리팩토링(컨텍스트 사용)
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx, frontend/src/components/attendancemanage/SessionManagementCard.jsx, frontend/src/components/attendancemanage/SessionSettingCard.jsx, frontend/src/components/attendancemanage/SessionManagementCard.module.css
여러 컴포넌트가 prop 기반 API에서 컨텍스트(useAttendance) 기반으로 전환됨; 서명 단순화(불필요한 props 제거), UI에 라운드 선택/수정/추가 버튼 및 .selectGroup 스타일 추가
새 모달 / UI 컴포넌트 추가
frontend/src/components/attendancemanage/RoundDayPicker.jsx, frontend/src/components/attendancemanage/RoundModifyModal.jsx, frontend/src/components/attendancemanage/SessionModifyModal.jsx
RoundDayPicker: 다중 날짜 선택 후 라운드 생성 로직 추가(로컬 YYYY-MM-DD 정규화 등). RoundModifyModal: 이름 변경 및 삭제 토스트/ESC 핸들링 추가. SessionModifyModal: 세션 시간/기본 분 수정 및 삭제 기능 추가
토스트/확인 컴포넌트 추가
frontend/src/components/attendancemanage/ConfirmationToast.jsx, frontend/src/components/attendancemanage/ConfirmationToast.module.css
삭제 확인용 커스텀 토스트(UI 및 스타일) 추가
스타일 조정
frontend/src/components/VerificationModal.module.css
모달 패딩/헤더 여백/버튼 크기 변경, 기본 액션 색상 및 비활성 상태 색상 변경, .modifyButtonGroup 추가

Sequence Diagram(s)

sequenceDiagram
    participant Page as AttendanceManage 페이지
    participant Provider as AttendanceProvider (컨텍스트)
    participant SessionCard as SessionManagementCard
    participant RoundCard as AttendanceManagementCard
    participant Modals as Modal 컴포넌트들

    Page->>Provider: AttendanceProvider로 래핑
    Provider->>Provider: 초기 세션/라운드 데이터 보유

    SessionCard->>Provider: useAttendance()로 데이터/액션 요청
    Provider-->>SessionCard: sessions, 선택/모달 제어 함수

    RoundCard->>Provider: useAttendance()로 selectedRound, participants 요청
    Provider-->>RoundCard: selectedRound, handleAttendanceChange

    rect rgba(150,255,200,0.2)
    Note over SessionCard,Modals: 라운드 추가 흐름
    SessionCard->>Modals: RoundDayPicker 오픈
    Modals->>Provider: handleAddRounds(newRounds)
    Provider->>Provider: 해당 세션에 라운드 추가 후 정렬
    Provider-->>RoundCard: 상태 업데이트(리렌더)
    end

    rect rgba(255,220,220,0.2)
    Note over RoundCard,Provider: 출석 변경 흐름
    RoundCard->>Provider: handleAttendanceChange(memberId, value)
    Provider->>Provider: 현재 선택 라운드의 참가자 출석 상태 업데이트
    Provider-->>RoundCard: 변경 반영
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • 주의 검토 항목:
    • AttendanceContext의 useImmer 기반 상태 변경 로직(특히 handleAttendanceChange, handleAddRounds, handleRoundChange)
    • RoundDayPicker의 날짜 정규화(지역 시간 → YYYY-MM-DD) 및 DayPicker 설정(미래 날짜 제한, 다중 선택)
    • RoundModifyModal 이름 변경 및 onDelete 통합(토스트 생명주기와 ESC 처리)
    • SessionModifyModal의 입력 유효성 검사 및 삭제 흐름
    • 여러 컴포넌트의 prop 제거로 인한 호출부 누락/임포트 경로 문제

Possibly related PRs

Suggested labels

FE

Suggested reviewers

  • DongEun02
  • gxuoo

Poem

🐰 토끼 한 마리 깡총 뛰며 고함해요:
컨텍스트 품에 안겨 상태가 편안해졌네,
라운드는 날짜를 모아 춤추고,
모달은 확인하고, 토스트는 속삭이네,
작은 변경이 큰 흐름을 만들었지요. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 PR의 주요 변경 사항을 명확하게 반영하고 있습니다. 세션/회차 수정, 삭제, 추가 기능 구현이라는 핵심 기능들을 구체적으로 나열하고 있으며, 코드 변경 사항들이 이 목표들과 일치합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch attendance-manage-ui-fix

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example:

"Create a concise high-level summary as a bullet-point list. Then include a Markdown table showing lines added and removed by each contributing author."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

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/SessionSettingCard.jsx (1)

38-53: 검증에 사용한 trim 결과를 실제 생성에도 사용하면 데이터가 더 일관적입니다

handleCreateClick에서 title = sessionTitle.trim()으로 공백을 제거해 검증하지만, 실제 생성에는 다시 원본 sessionTitle을 전달하고 있어 앞뒤 공백이 그대로 저장될 수 있습니다. 아래처럼 title을 넘기면 UI와 저장 값이 일치해집니다.

-    handleAddSession(sessionTitle, {
+    handleAddSession(title, {
       hh: hh.padStart(2, '0'),
       mm: mm.padStart(2, '0'),
       ss: ss.padStart(2, '0'),
       availableTimeMm: availableMinute,
     });

또한 현재 숫자 검증이 parseInt 기반이라 "10aa" 같은 입력도 통과해 "10aa:..." 형태로 저장될 여지가 있습니다. 나중에 필요하다면 숫자만 허용하는 정규식 검사나 input 타입을 number로 제한하는 것도 고려해 주세요.

🧹 Nitpick comments (11)
frontend/src/components/VerificationModal.module.css (2)

100-121: 중복된 색상 선언 제거

.button 클래스에서 color 속성이 111번 줄과 114번 줄에 중복으로 선언되어 있습니다. 하나로 통합하면 코드의 일관성이 향상됩니다.

 .button {
   display: inline-flex;
   padding: 16px 24px;
   justify-content: center;
   align-items: center;
   gap: 10px;
   border-radius: 8px;

   width: 100px;
   height: 42px;

   color: white;
   border: none;
-
-  color: #fff;
   font-family: Pretendard;
   font-size: 16px;
   font-style: normal;
   font-weight: 500;
   line-height: 100%; /* 16px */
   letter-spacing: -0.48px;
 }

108-109: 버튼 크기 축소로 인한 터치 타겟 크기 확인 필요

버튼 크기가 132x48px에서 100x42px로 축소되었습니다. WCAG 접근성 기준상 권장되는 최소 터치 타겟은 44x44px이므로, 현재 42px 높이가 이 기준 이하입니다. 모바일 또는 터치 기기에서의 사용성을 고려하여 크기를 검토해주시기 바랍니다.

frontend/src/components/attendancemanage/SessionModifyModal.jsx (2)

29-51: 유효성 검사 로직이 다른 컴포넌트와 중복됩니다.

SessionSettingCard, RoundModifyModal, SessionModifyModal에 거의 동일한 시간/분/초/분 유효성 검사 로직이 반복되고 있습니다. 나중에 규칙 변경이나 메시지 수정 시 누락 위험이 있으니, 공용 유틸 함수나 훅으로 분리해서 재사용하는 것을 고려해 주세요.


93-139: id 중복으로 인한 경미한 접근성 이슈 가능성

id="sessionStartTime"/id="sessionAvailableTime" 조합이 다른 컴포넌트(예: SessionSettingCard, RoundModifyModal)에도 동일하게 존재하는데, 페이지/모달가 동시에 열리면 DOM 상에서 id가 중복될 수 있습니다. 큰 문제는 아니지만 라벨 연결 및 스크린 리더 측면에서 혼동 여지가 있어, 모달 쪽은 sessionStartTime-modal 같은 고유한 id로 분리하는 것을 추천드립니다.

frontend/src/pages/AttendanceManage.jsx (1)

39-57: 모달 렌더링 시 editingRound/editingSession에 대한 가드를 추가하면 더 안전합니다.

현재는 isRoundModifyModalOpen 또는 isSessionModifyModalOpen만으로 모달을 렌더링합니다. 컨텍스트 사용 패턴상 editingRound/editingSession이 항상 세팅된 상태에서만 열리긴 하지만, 미래에 로직이 바뀌면 round.startTime/session.defaultStartTime 접근에서 런타임 에러가 날 수 있습니다.

아래처럼 편의상 한 줄만 바꿔 두면 방어 코드로 도움이 됩니다.

-      {isRoundModifyModalOpen && (
+      {isRoundModifyModalOpen && editingRound && (
         <RoundModifyModal
           styles={styles}
           onClose={closeRoundModifyModal}
           round={editingRound}
           onSave={handleRoundChange}
           onDelete={handleDeleteRound}
         />
       )}
-      {isSessionModifyModalOpen && (
+      {isSessionModifyModalOpen && editingSession && (
         <SessionModifyModal
           styles={styles}
           onClose={closeSessionModifyModal}
           session={editingSession}
           onSave={handleSessionChange}
           onDelete={handleDeleteSession}
         />
       )}
frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)

1-86: 회차 다중 생성 플로우와 날짜 포맷 처리 전반적으로 잘 구성되어 있습니다

선택된 세션 여부와 선택된 날짜 배열을 먼저 검증하고, 타임존 오프셋을 보정한 뒤 YYYY-MM-DD 문자열로 저장하는 흐름이 명확하고 안정적입니다. handleAddRounds(selectedSessionId, newRounds)로 컨텍스트에 위임하는 구조도 자연스럽습니다.

다만 한 세션에서 같은 날짜/시간 조합의 회차를 중복 생성하지 않으려면, newRounds를 만들 때 기존 session.rounds와 중복 여부를 한 번 필터링하는 것도 추후 요구사항에 따라 고려해 볼 수 있습니다.

frontend/src/components/attendancemanage/RoundModifyModal.jsx (1)

68-73: 삭제 플로우는 명확하지만 상위 onDelete 구현과의 연동만 한 번 확인해 주세요

handleDeleteClick에서 window.confirm으로 사용자 확인 후 onDelete(round.id)를 호출하고 모달을 닫는 흐름이 직관적입니다. 상위에서는 이 콜백에서 컨텍스트의 handleDeleteRound를 호출할 것으로 보이는데, 비동기 API 연동 시에는 다음을 같이 고려해 볼 수 있습니다.

  • 삭제 요청 중 중복 클릭을 막기 위해 삭제 버튼 비활성화 또는 로딩 상태 표시
  • 삭제 실패 시 모달을 닫지 않고 오류 안내 후 상태 롤백

또, 이 모달이 렌더링될 때 항상 roundonDelete가 정의되어 있다는 전제가 있으므로, 상위에서 editingRound가 설정된 경우에만 모달을 띄우는지 한 번만 점검해 주세요.

Also applies to: 135-148

frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (1)

3-7: 컨텍스트 기반 출석 변경 흐름이 단순하고 일관성 있습니다

selectedRound, participants, handleAttendanceChange를 컨텍스트에서 바로 가져와 카드 자체는 UI 렌더링에만 집중하도록 한 구조가 좋습니다. 회차 미선택 시와 참가자 없음 시 메시지도 명확합니다.

향후 출석 상태 종류(예: 지각, 조퇴 등)가 늘어날 가능성이 있다면, <option> 리스트를 상수로 분리하거나 컨텍스트/상위에서 내려받도록 해 두면 상태 종류가 바뀌었을 때 여러 컴포넌트를 동시에 수정하지 않아도 되어 유지보수가 더 쉬워질 것 같습니다.

Also applies to: 41-44

frontend/src/contexts/AttendanceContext.jsx (2)

143-175: 세션/회차 삭제 후 선택 상태는 리셋되지만 편집 상태까지 초기화할지 결정 필요

handleDeleteSession에서 selectedSessionId, selectedRoundnull로 리셋하고, handleDeleteRound에서도 selectedRound를 초기화하는 부분은 일관성이 좋습니다.

다만 모달이 열린 상태에서 삭제가 일어날 수 있다면(예: 세션 수정 모달이 열려 있는데 세션 삭제 버튼을 누르는 경우), editingSession, editingRound 같은 편집용 상태도 함께 초기화해 두면 이후 재사용 시 헷갈릴 여지가 더 줄어듭니다. 현재 호출 플로우 상에서 항상 모달을 먼저 닫고 삭제를 호출한다면 필수는 아니지만, 방어적으로 초기화해 두는 것도 한 가지 선택지입니다.


224-230: useAttendance에서 에러 메시지를 명시하면 디버깅에 더 유리합니다

컨텍스트가 설정되지 않은 경우 throw new Error();로만 던지면 스택 트레이스에는 원인이 드러나지 않아 디버깅이 어렵습니다. 예를 들어 다음처럼 메시지를 추가해 두면 어디서 문제인지 바로 파악하기 좋습니다.

if (context === null) {
  throw new Error('useAttendance는 AttendanceProvider 내에서만 사용할 수 있습니다.');
}

개발자 경험 측면에서 작은 차이지만 도움이 됩니다.

frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)

57-70: LGTM! 접근성 개선 제안

세션 수정 버튼의 검증 로직과 상태 업데이트 순서가 올바릅니다.

접근성을 위해 aria-label 추가를 고려하세요:

 <button
   type="button"
   className={styles.menuButton}
+  aria-label="세션 수정"
   onClick={() => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cdcfc8 and 8aaf706.

⛔ Files ignored due to path filters (2)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • frontend/src/assets/calendar-add-icon.svg is excluded by !**/*.svg
📒 Files selected for processing (12)
  • frontend/package.json (1 hunks)
  • frontend/src/components/VerificationModal.module.css (5 hunks)
  • frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (2 hunks)
  • frontend/src/components/attendancemanage/RoundDayPicker.jsx (1 hunks)
  • frontend/src/components/attendancemanage/RoundModifyModal.jsx (5 hunks)
  • frontend/src/components/attendancemanage/SessionManagementCard.jsx (4 hunks)
  • frontend/src/components/attendancemanage/SessionManagementCard.module.css (2 hunks)
  • frontend/src/components/attendancemanage/SessionModifyModal.jsx (1 hunks)
  • frontend/src/components/attendancemanage/SessionSettingCard.jsx (2 hunks)
  • frontend/src/contexts/AttendanceContext.jsx (1 hunks)
  • frontend/src/pages/AttendanceManage.jsx (1 hunks)
  • frontend/src/pages/AttendanceManage.module.css (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
frontend/src/components/attendancemanage/SessionModifyModal.jsx (2)
frontend/src/components/attendancemanage/RoundModifyModal.jsx (6)
  • hh (22-22)
  • mm (23-23)
  • ss (24-24)
  • isFormValid (29-47)
  • handleModifyClick (49-67)
  • handleDeleteClick (68-73)
frontend/src/components/attendancemanage/SessionSettingCard.jsx (4)
  • hh (9-9)
  • mm (10-10)
  • ss (11-11)
  • isFormValid (14-36)
frontend/src/pages/AttendanceManage.jsx (7)
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (2)
  • useAttendance (6-7)
  • AttendanceManagementCard (5-66)
frontend/src/components/attendancemanage/RoundDayPicker.jsx (2)
  • useAttendance (10-11)
  • RoundDayPicker (9-84)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (2)
  • useAttendance (6-17)
  • SessionManagementCard (5-122)
frontend/src/components/attendancemanage/SessionSettingCard.jsx (2)
  • useAttendance (6-6)
  • SessionSettingCard (5-129)
frontend/src/contexts/AttendanceContext.jsx (15)
  • useAttendance (225-231)
  • useAttendance (225-231)
  • isRoundModifyModalOpen (56-56)
  • closeRoundModifyModal (101-101)
  • editingRound (55-55)
  • handleRoundChange (77-87)
  • handleDeleteRound (157-175)
  • isSessionModifyModalOpen (58-58)
  • closeSessionModifyModal (104-104)
  • editingSession (57-57)
  • handleSessionChange (89-98)
  • handleDeleteSession (143-156)
  • isAddRoundsModalOpen (60-60)
  • AttendanceProvider (51-222)
  • AttendanceProvider (51-222)
frontend/src/components/attendancemanage/RoundModifyModal.jsx (1)
  • RoundModifyModal (4-155)
frontend/src/components/attendancemanage/SessionModifyModal.jsx (1)
  • SessionModifyModal (4-159)
frontend/src/components/attendancemanage/SessionSettingCard.jsx (1)
frontend/src/contexts/AttendanceContext.jsx (3)
  • useAttendance (225-231)
  • useAttendance (225-231)
  • handleAddSession (110-132)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (2)
frontend/src/contexts/AttendanceContext.jsx (6)
  • useAttendance (225-231)
  • useAttendance (225-231)
  • openAddRoundsModal (106-106)
  • openSessionModifyModal (103-103)
  • selectedRound (54-54)
  • openRoundModifyModal (100-100)
frontend/src/pages/AttendanceManage.jsx (1)
  • useAttendance (15-27)
frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)
frontend/src/contexts/AttendanceContext.jsx (6)
  • useAttendance (225-231)
  • useAttendance (225-231)
  • sessions (52-52)
  • selectedSessionId (53-53)
  • handleAddRounds (133-141)
  • closeAddRoundsModal (108-108)
frontend/src/contexts/AttendanceContext.jsx (6)
frontend/src/pages/OAuthSuccess.jsx (1)
  • p (5-5)
frontend/src/components/attendancemanage/SessionSettingCard.jsx (2)
  • sessionTitle (8-8)
  • useAttendance (6-6)
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (1)
  • useAttendance (6-7)
frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)
  • useAttendance (10-11)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)
  • useAttendance (6-17)
frontend/src/pages/AttendanceManage.jsx (1)
  • useAttendance (15-27)
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (1)
frontend/src/contexts/AttendanceContext.jsx (3)
  • useAttendance (225-231)
  • useAttendance (225-231)
  • handleAttendanceChange (62-75)
frontend/src/components/attendancemanage/RoundModifyModal.jsx (1)
frontend/src/components/attendancemanage/SessionModifyModal.jsx (1)
  • handleModifyClick (53-71)
🔇 Additional comments (11)
frontend/src/components/VerificationModal.module.css (3)

137-139: 삭제/초기화 작업을 위한 색상 변경 - 승인

resetPasswordButton 배경색을 파란색(#1d80f4)에서 빨간색(#ff2c4c)으로 변경한 것은 의미론적으로 적절합니다. 빨간색은 파괴적인 작업(삭제, 초기화)을 시각적으로 강조하는 UI 관례에 부합합니다.


179-183: 새로운 버튼 그룹 유틸리티 클래스 추가 - 승인

.modifyButtonGroup 클래스가 새로 추가되어 여러 버튼을 일관되게 배치할 수 있게 되었습니다. Flex 레이아웃과 적절한 여백 설정으로 회차 수정/삭제 모달의 버튼 그룹에 사용되는 것으로 보입니다.

여러 관련 모달 컴포넌트(RoundModifyModal, SessionModifyModal 등)에서 이 클래스가 일관되게 사용되고 있는지 확인해주시기 바랍니다.


14-20: 모달 레이아웃 및 간격 조정 - 승인

모달 패딩을 56px에서 30px 50px로, 헤더 하단 마진을 52px에서 30px로 조정하여 새로운 디자인 시스템에 맞춘 여백 설정입니다. 이는 세션/회차 관리 UI 전체의 일관된 디자인 개선에 기여합니다.

frontend/src/components/attendancemanage/SessionManagementCard.module.css (1)

8-11: 세션 선택 영역 레이아웃 및 선택 행 스타일이 일관성 있게 개선되었습니다.

.selectGroup으로 세션 셀렉트와 메뉴 버튼을 가로 정렬하고, .selectedRoundcursor: pointer를 추가해 클릭 가능한 행이라는 점이 명확해졌습니다. SessionManagementCard.jsx에서의 사용과도 잘 맞습니다.

Also applies to: 88-90

frontend/src/pages/AttendanceManage.module.css (1)

60-77: 공통 헤더 및 아이콘 버튼 스타일이 잘 추출되었습니다.

.header.iconButton을 페이지 레벨에서 공통으로 정의해서 SessionSettingCard, SessionManagementCard, AttendanceManagementCard의 헤더 레이아웃이 일관되게 맞춰진 점이 좋습니다. 아이콘 크기를 20px로 고정한 것도 레이아웃 안정성에 도움이 됩니다.

frontend/src/pages/AttendanceManage.jsx (1)

1-27: 페이지 레벨에서 컨텍스트로 상태를 집약한 구조가 깔끔합니다.

AttendanceProvider로 페이지 전체를 감싸고, 실제 뷰는 AttendanceContent에서 useAttendance로만 상태를 읽도록 분리한 구조가 명확합니다. 세션 생성/관리/출석 카드가 모두 컨텍스트 하나를 공유하므로, 이후 API 연동 시에도 데이터 흐름을 따라가기가 쉬워질 것 같습니다.

Also applies to: 62-71

frontend/src/components/attendancemanage/SessionSettingCard.jsx (1)

3-7: AttendanceContext 연동으로 props 의존성이 줄어든 점은 좋습니다

이제 세션 추가 로직을 컨텍스트에서 직접 가져와 상위 컴포넌트가 훨씬 단순해집니다. 이 컴포넌트가 항상 AttendanceProvider 내부에서만 렌더링되도록 페이지 구성만 한 번 확인해 주세요.

frontend/src/components/attendancemanage/RoundModifyModal.jsx (1)

4-21: 시간 파싱을 객체 기반으로 바꾼 점이 가독성과 안정성에 도움이 됩니다

parseTime에서 항상 { h, m, s } 형태의 객체를 반환하고, round.startTime이 falsy여도 '00:00:00'으로 안전하게 떨어지도록 한 구조가 좋습니다. 디폴트 값까지 구조 분해 할당으로 명시해 둬서 초기 렌더 시 에러 가능성도 줄었습니다.

frontend/src/components/attendancemanage/SessionManagementCard.jsx (3)

1-3: LGTM!

컨텍스트 기반 아키텍처로의 전환을 위한 임포트가 적절하게 구성되어 있습니다.


5-17: LGTM!

컨텍스트 기반 상태 관리로의 전환이 잘 이루어졌습니다. 모든 필요한 상태와 액션이 적절하게 구조 분해되어 있습니다.


83-116: LGTM!

회차 테이블의 조건부 렌더링과 선택 로직이 잘 구현되어 있습니다. 데이터가 없을 때의 폴백 메시지도 적절합니다.

Comment on lines +14 to +18
"date-fns": "^4.1.0",
"immer": "^10.2.0",
"pretendard": "^1.3.9",
"react": "^19.1.1",
"react-day-picker": "^9.11.1",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

새로 추가된 의존성 구성이 사용처와 잘 맞습니다.

react-day-pickerRoundDayPicker에서 바로 사용되고 있고, date-fns도 날짜 처리 유틸로 무난한 선택이라 방향은 좋아 보입니다. 실제로 쓰이지 않는 의존성이 없는지와 버전 호환성만 한 번 점검해 두면 좋겠습니다.

다음 스크립트로 사용처/중복 여부를 빠르게 확인해 볼 수 있습니다:


🏁 Script executed:

#!/bin/bash
# date-fns / react-day-picker 실제 사용 위치 확인
rg "date-fns" frontend/src -n || echo "date-fns 사용처 없음"
rg "react-day-picker" frontend/src -n || echo "react-day-picker 사용처 없음"

Length of output: 222


더 광범위하게 확인하겠습니다. date-fns가 테스트나 다른 디렉토리에서 사용되는지, 그리고 react-day-picker 내부 의존성으로 필요한지 검증하겠습니다.

이 출력을 기반으로 최종 검증을 마치겠습니다.


🏁 Script executed:

#!/bin/bash
# 전체 repository에서 date-fns 검색 (frontend 폴더만)
echo "=== frontend 전체에서 date-fns 검색 ==="
rg "date-fns" frontend -n --type json || echo "JSON 파일에서도 없음"

# react-day-picker의 실제 사용 위치 다시 확인
echo -e "\n=== react-day-picker 상세 검색 ==="
rg "react-day-picker" frontend -n -B2 -A2

# package.json에서 react-day-picker 의존성 확인
echo -e "\n=== package.json에서 의존성 확인 ==="
cat frontend/package.json | grep -A 50 '"dependencies"' | grep -E '(date-fns|react-day-picker)'

Length of output: 262


이전 스크립트에 `` 태그가 있어서 저장소가 복제되지 않았습니다. 다시 올바른 설정으로 검증하겠습니다.


🏁 Script executed:

#!/bin/bash
# 저장소 경로 확인
echo "=== 현재 디렉토리 ==="
pwd
ls -la | head -20

# 전체 repository에서 date-fns 검색
echo -e "\n=== date-fns 사용 위치 전체 검색 ==="
rg "date-fns" . -n --type=js --type=jsx --type=ts --type=tsx 2>/dev/null || echo "date-fns 사용처 없음"

# react-day-picker 사용 위치 확인
echo -e "\n=== react-day-picker 사용 위치 ==="
rg "react-day-picker" . -n --type=js --type=jsx --type=ts --type=tsx -B1 -A1

# RoundDayPicker.jsx에서 date-fns 관련 코드 확인
echo -e "\n=== RoundDayPicker.jsx 전체 내용 확인 ==="
find . -name "RoundDayPicker.jsx" -exec cat {} \;

Length of output: 3125


react-day-picker이 date-fns에 의존하는지 확인하겠습니다.


🏁 Script executed:

#!/bin/bash
# react-day-picker package.json에서 dependencies 확인
echo "=== react-day-picker 의존성 확인 ==="
find . -path "*/node_modules/react-day-picker/package.json" -exec cat {} \; 2>/dev/null | grep -A 20 '"dependencies"' || echo "node_modules에서 react-day-picker 찾을 수 없음"

# package.json 파일 찾기
echo -e "\n=== package.json 찾기 ==="
find . -name "package.json" -type f -not -path "*/node_modules/*" | head -5

Length of output: 120


react-day-picker의 의존성을 확인하겠습니다.


🌐 Web query:

react-day-picker 9.11.1 dependencies date-fns required

💡 Result:

Yes — in v9 (including 9.11.1) react-day-picker includes date-fns as a direct dependency (not a peer dependency), so date-fns is required by the package. The v9 changelog states date-fns was moved into dependencies, and package listings for 9.11.1 show date-fns (plus @date-fns/tz and date-fns-jalali) as dependencies. [1][2][3]

Sources:
[1] react-day-picker changelog (v9: "Moved date-fns from peer dependencies to dependencies").
[2] jsDocs/npm package listing for react-day-picker 9.11.1 (dependencies include date-fns, @date-fns/tz, date-fns-jalali).
[3] Maven/NPM metadata for react-day-picker 9.11.1 (compile dependencies include date-fns and related packages).


date-fnsreact-day-picker의 내부 의존성이므로 직접 추가할 필요가 없습니다.

react-day-picker v9.11.1은 date-fns를 직접 의존성으로 포함하고 있습니다. 코드 검증 결과 date-fns가 어디서도 직접 import되거나 사용되지 않음을 확인했습니다. RoundDayPicker.jsx는 native JavaScript Date API를 사용하고 있습니다.

제거 대상: frontend/package.json"date-fns": "^4.1.0" (14번 줄)

  • react-day-picker가 자동으로 관리하는 의존성이므로 직접 추가하면 불필요한 중복 발생
  • react-day-picker와의 버전 불일치 가능성
🤖 Prompt for AI Agents
In frontend/package.json around lines 14 to 18, remove the direct dependency
entry for "date-fns": "^4.1.0" because react-day-picker already bundles/depends
on it and there are no direct imports; after removing the line, run your package
manager (npm or yarn/pnpm) to update node_modules and the lockfile
(package-lock.json/yarn.lock/pnpm-lock.yaml) and verify the app builds/tests
pass to ensure no missing imports remain.

Copy link
Contributor

@gxuoo gxuoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기능 위주로 리뷰를 드렸습니다. 그리고 이전에도 말씀드린 것 같긴 한데, 현재 출석관리(관리자) 페이지 내부에서 왼쪽 공백과 오른쪽 공백의 비율이 불균형하다고 생각이 들어서, 피그마 디자인을 그대로 따라하는 것도 좋지만 양쪽 공백은 어느 정도 맞춰서 페이지 내부를 최대한 활용하는 방식으로 디자인을 조금 고쳐보는 건 어떤지 한 번 생각해보시는 것도 좋을 것 같습니다.

고생하셨습니다! 👍👍

};
});

handleAddRounds(selectedSessionId, newRounds);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재는 먼저 선택된 날짜 순서로 세션이 등록되고 있어서 세션이 추가 됐을 때, 날짜 순서로 정렬이 된 후에 추가되면 좋을 거 같아요.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

회차를 추가하기 전에 정렬하면 추가한 뒤에는 다른 회차들까지 봤을 때 정렬되지 않을 수 있으니까 회차 정보가 수정되거나 회차가 추가되었을때 정렬되도록 구현했습니다!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다~ 고생하셨어요!

Comment on lines +69 to +72
if (window.confirm('이 회차를 정말로 삭제하시겠습니까?')) {
onDelete(round.id);
onClose();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

react-toastify 라이브러리에 대해 검색해보시는 걸 추천드립니다! UX적으로 alert나 confirm은 그렇게 좋지 않다고 개인적으로 생각하고 있거든요.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alert는 react-toastify 사용하고 confirm은 react-confirm-alert 사용하는 게 좋아보입니다!

@GamjaIsMine02 GamjaIsMine02 force-pushed the attendance-manage-ui-fix branch from 8aaf706 to dd09635 Compare November 17, 2025 17:31
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
frontend/src/contexts/AttendanceContext.jsx (2)

8-49: 세션 location 필드 스키마를 새로 생성되는 세션과 일관되게 맞추는 게 좋겠습니다

현재 목데이터의 sessionData에는 location 필드가 있지만, handleAddSession에서 생성하는 새 세션에는 location이 없습니다. 지금은 location을 어디서도 사용하지 않아 기능상 문제는 없지만, 이후 위치 기반 기능을 붙이면 새 세션만 locationundefined인 상태가 되어 예외 처리가 필요해질 수 있습니다.

  • location이 불필요하다면 목데이터에서 제거하거나
  • 모든 세션에 위치가 있어야 한다면 handleAddSession에서 기본값(null 혹은 기본 좌표)까지 포함해 생성하는 쪽을 추천합니다.

이 부분은 이전 리뷰에서도 한 번 언급되었던 내용이라, 이번에 정리해 두면 좋겠습니다.

Also applies to: 118-136


77-95: handleRoundChange / handleDeleteRoundselectedSessionId 클로저 의존성은 한 번 더 점검이 필요해 보입니다

두 핸들러 모두 클로저에 캡처된 selectedSessionId를 기준으로 세션을 찾고 있습니다. Round/Session 수정/삭제 모달이 열린 상태에서 세션 선택을 바꿀 수 있는 UI라면:

  • 수정: 모달은 A 세션의 회차를 보여주고 있는데, selectedSessionId가 B로 바뀌면 B 세션의 회차를 수정하거나, 해당 ID가 없으면 아무 것도 안 하는 상황이 생길 수 있고
  • 삭제: 마찬가지로 의도와 다른 세션을 대상으로 동작하거나, 조용히 실패할 수 있습니다.

UI 쪽에서 모달이 열려 있는 동안 세션 변경을 막고 있다면 현재 구조도 괜찮지만, 그렇지 않다면 아래 중 하나를 고려해 볼 만합니다.

  • handleRoundChange(sessionId, updateRoundData) / handleDeleteRound(sessionId, roundId)처럼 세션 ID를 인자로 받도록 변경하고, RoundModifyModal 쪽에서 모달을 열 때의 세션 ID를 함께 넘기거나
  • 세션 선택 핸들러에서 isRoundModifyModalOpen / isSessionModifyModalOpen이 true면 early-return 하는 가드를 넣어, 모달이 열린 상태에서 세션이 바뀌지 않게 하기

이 이슈도 이전 리뷰에서 이미 한 차례 언급된 부분이라, 현재 UI 플로우 기준으로 실제로 문제가 없는지 한 번만 다시 확인해 보시면 좋겠습니다.

Also applies to: 171-185

🧹 Nitpick comments (15)
frontend/src/components/VerificationModal.module.css (2)

184-185: 비어있는 스타일 규칙 제거.

.modifyButtonGroup button:disabled 규칙이 정의되지 않아 기능하지 않습니다. 이 규칙이 필요하지 않다면 제거하거나, 스타일이 필요한 경우 구현하세요.

-.modifyButtonGroup button:disabled {
-}

111-114: .button 클래스에서 중복된 color 속성 제거.

color: white; 속성이 줄 111과 114에서 중복 정의되어 있습니다. 다음과 같이 정리하세요:

 .button {
   display: inline-flex;
   padding: 16px 24px;
   justify-content: center;
   align-items: center;
   gap: 10px;
   border-radius: 8px;

   width: 100px;
   height: 42px;

   color: white;
   border: none;

-  color: #fff;
   font-family: Pretendard;
frontend/src/contexts/AttendanceContext.jsx (3)

118-140: 새 세션 생성 시 입력값 사용을 조금 더 일관되게 다듬을 수 있습니다

SessionSettingCard에서 이미 title, availableMinute로 trim/parse를 한 뒤, 여기에서는 원본 sessionTitleavailableTimeMm를 다시 받아 사용하는 형태입니다. 큰 문제는 아니지만:

  • handleAddSession 호출 시 sessionTitle 대신 미리 trim된 title을 넘기면, 앞뒤 공백이 포함되지 않아 더 일관적이고
  • availableTimeMm는 현재 숫자가 들어오는데 parseInt를 한 번 더 하는 구조라, 인터페이스를 "분 단위 숫자"로 고정하고 여기서는 그냥 그대로 대입해도 됩니다 (defaultAvailableMinutes: availableMinute 형태).

또, 주석 처리된 기본 회차 템플릿은 실제로 사용하지 않을 계획이라면 정리해 두는 편이 코드 가독성에 도움이 될 것 같습니다.


157-170: 세션 삭제 후 selection 초기화 조건을 조금 더 좁히는 것도 고려해 볼 만합니다

현재 구현은 어떤 세션을 삭제하더라도 항상 selectedSessionIdselectedRoundnull로 초기화합니다. 보통은 "선택된 세션을 삭제"하는 플로우일 것 같지만, 추후 UI에서 선택과 무관하게 다른 세션을 바로 삭제할 수 있게 되면, 사용자가 보고 있던 세션이 아닌데도 selection이 풀리는 UX가 나올 수 있습니다.

예를 들어 아래처럼 삭제 대상이 현재 선택된 세션일 때만 selection을 초기화하는 식으로 좁히는 것도 한 가지 방법입니다.

if (selectedSessionId === sessionId) {
  setSelectedSessionId(null);
  setSelectedRound(null);
}

현재 플로우가 어떤지에 따라 선택적으로 고려해 보셔도 좋겠습니다.


239-244: useAttendance에서 에러 메시지를 명시하면 디버깅에 더 도움이 됩니다

지금은 context === null일 때 new Error()만 던지고 있어서, 실제 런타임 에러 로그만 보고는 원인을 파악하기가 어렵습니다. 예를 들어 다음처럼 메시지를 넣어두면, Provider로 감싸지 않고 훅을 썼을 때 바로 원인을 알 수 있어 좋습니다.

if (context === null) {
  throw new Error('AttendanceProvider로 감싸지 않은 컴포넌트에서 useAttendance를 호출했습니다.');
}
frontend/src/components/attendancemanage/ConfirmationToast.module.css (1)

1-46: CSS 구조는 전반적으로 깔끔하고, flex 컨테이너의 justify-items만 정리하면 좋겠습니다

토스트 레이아웃과 버튼 스타일이 단순하고 읽기 좋아서 유지보수하기 편해 보입니다. 한 가지 정도만 정리하면 좋을 것 같은 부분은:

  • .toastContainerdisplay: flex인데, justify-items는 CSS Grid용 속성이라 flex 컨테이너에서는 효과가 없습니다. 필요 없다면 제거하거나, 의도하신 게 메인 축 정렬이라면 justify-content로 바꾸는 쪽을 추천드립니다.

그 외 버튼/hover 색상 정의는 일관되고 명확합니다.

frontend/src/components/attendancemanage/ConfirmationToast.jsx (1)

3-25: 확인 토스트 컴포넌트 구조가 명확하고, 비동기 처리까지 고려한 소소한 개선 여지가 있습니다

현재 구현은 단순·명료하고, ConfirmationToast를 다른 삭제/확인 플로우에도 쉽게 재사용할 수 있을 것 같아 좋습니다.

만약 onConfirm가 비동기 함수일 가능성이 있다면, 아래처럼 Promise 완료 이후에 토스트를 닫도록 하면 로딩/에러 처리 시에도 동작이 더 예측 가능해집니다.

-          onClick={() => {
-            onConfirm();
-            closeToast();
-          }}
+          onClick={async () => {
+            await Promise.resolve(onConfirm());
+            closeToast();
+          }}

동기 콜백만 쓴다면 지금 구현 그대로도 충분합니다.

frontend/src/pages/AttendanceManage.jsx (2)

16-60: 컨텍스트 기반으로 카드/모달을 묶어낸 구조가 이해하기 쉽습니다

AttendanceContent에서 컨텍스트 값들을 한 번에 꺼내서 카드 3개와 모달들을 모두 연결한 구조라, 출석 관리 화면의 데이터 플로우를 따라가기가 훨씬 수월해졌습니다.

한 가지 정도만 체크해 보면 좋을 부분은, 모달 렌더링이 isRoundModifyModalOpen / isSessionModifyModalOpen 플래그만 보고 있어서 editingRoundeditingSession이 우연히 null인 상태로 모달이 열릴 수 있는지 여부입니다. 그런 경로가 있다면:

  • 플래그를 켜기 전에 항상 editing 객체를 먼저 세팅하거나
  • 모달 쪽에서 round/session이 없으면 바로 onClose()를 호출하는 식의 방어 로직을 추가

하는 것도 고려해 볼 만합니다.


64-80: 페이지 레벨의 ToastContainer 위치는 적절해 보이며, 중복 여부만 한 번 확인해 주세요

AttendanceManage 내부에 ToastContainer를 두어서 이 페이지에서 발생하는 토스트를 자체적으로 관리하는 구조는 직관적입니다. 다만 앱 루트(App 등)에 이미 다른 ToastContainer가 있다면 토스트가 중복 렌더링될 수 있으니:

  • 전역에 하나만 두고 재사용할지,
  • 특정 페이지 전용으로 둘지

정책을 한 번 정리해 두시면 좋겠습니다.

frontend/src/components/attendancemanage/SessionSettingCard.jsx (1)

38-53: 세션 생성 시 이미 정제해 둔 값들을 재사용하면 인터페이스가 더 일관될 것 같습니다

handleCreateClick에서:

  • title = sessionTitle.trim()
  • availableMinute = parseInt(availableTimeMm, 10)

으로 검증용 값을 따로 만들고 있는데, 실제 handleAddSession 호출 시에는 다시 원본 sessionTitleavailableTimeMm를 넘기고 있습니다.

크게 문제 되진 않지만, 아래처럼 정제된 값을 그대로 넘기면 조금 더 예측 가능한 인터페이스가 됩니다.

// 예시
if (!isFormValid(title, hour, minute, second, availableMinute)) return;

handleAddSession(title, {
  hh: hh.padStart(2, '0'),
  mm: mm.padStart(2, '0'),
  ss: ss.padStart(2, '0'),
  // 숫자를 넘기고 싶다면 이름도 맞춰주는 것도 한 방법입니다.
  availableTimeMm: availableMinute,
});

혹은 context 쪽에서 아예 "분 단위 숫자"를 받는 형태(defaultAvailableMinutes: availableMinute)로 계약을 명확히 해 두는 것도 좋습니다.

frontend/src/components/attendancemanage/SessionModifyModal.jsx (2)

53-91: 세션 시간/인정 시간 검증 및 저장 로직 잘 정리되어 있습니다

parseInt로 숫자 변환 후 isFormValid에서 범위 체크하고, onSavedefaultAvailableMinutes: availableMinute처럼 숫자 값을 넘기도록 바꾼 부분이 이전 타입 불일치 가능성을 잘 해소하고 있습니다.
다만 parseTime/isFormValid/HH:MM:SS 포맷팅 로직이 RoundModifyModal과 거의 동일하니, 향후에는 공통 유틸/훅으로 분리하면 유지보수성이 더 좋아질 것 같습니다.


33-51: ESC + 토스트 상태 관리 흐름이 일관적이며, UX 측면에서 약간의 여지가 있습니다

activeToastId로 토스트 활성 상태를 추적하면서 ESC 입력 시 토스트 → 모달 순으로 닫고, 토스트가 떠 있는 동안 상단 닫기/삭제/완료 버튼을 비활성화한 구조가 잘 짜여 있습니다.
추가로, 토스트가 없을 때 오버레이 영역 클릭 시 onClose를 호출하는 패턴(다른 모달들과 통일)까지 들어가면 사용자가 모달을 닫는 경로가 더 자연스러울 것 같습니다.

Also applies to: 96-116, 118-195

frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)

16-29: ESC 처리와 버튼 UX는 기본은 갖췄고, 버튼 비활성화도 고려해볼 수 있습니다

ESC 키로 모달을 닫도록 useEffect에서 전역 키다운 리스너를 등록/해제하는 부분이 깔끔합니다.
다만 현재는 날짜를 하나도 선택하지 않아도 "추가" 버튼이 활성이고, 클릭 시에야 alert가 뜨므로, selectedRounds.length === 0일 때 버튼을 비활성화하는 정도의 UX 개선을 고려해볼 수 있습니다.

Also applies to: 78-95

frontend/src/components/attendancemanage/RoundModifyModal.jsx (1)

33-51: 회차 삭제 플로우와 ESC/토스트 제어가 잘 연결되어 있습니다

삭제 시 ConfirmationToast를 띄우고, 확인 시 onDelete(round.id)onClose()를 호출하며, activeToastId를 통해 ESC 입력 시 토스트 → 모달 순으로 닫는 흐름이 자연스럽습니다. 토스트가 떠 있는 동안 닫기/삭제/완료 버튼을 비활성화한 것도 이중 액션을 막는 데 도움이 됩니다.
이 컴포넌트 역시 parseTime/검증/토스트 + ESC 패턴이 SessionModifyModal과 거의 동일하므로, 장기적으로는 공통 훅/헬퍼로 추출하는 것을 고려해볼 만합니다.

Also applies to: 92-112, 119-191

frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (1)

3-8: 컨텍스트 기반 출석 변경 플로우가 단순하고 명확합니다

useAttendance에서 selectedRound, participants, handleAttendanceChange를 받아와 사용하고, 선택된 회차가 없을 때/참가자가 없을 때 각각 다른 안내 문구를 보여주는 분기 구조가 직관적입니다. 셀렉트에서 바로 handleAttendanceChange(memberId, e.target.value)를 호출하는 부분도 컨텍스트 구현(handleAttendanceChange)과 잘 맞습니다.
한 가지 전제만 공유하자면, participants가 항상 배열(기본값 [])로 초기화되어 있어야 participants.length 접근에서 런타임 에러가 나지 않습니다. 컨텍스트 쪽에서 이미 그렇게 처리되어 있다면 현재 구현으로 충분해 보입니다.

Also applies to: 25-61

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8aaf706 and dd09635.

⛔ Files ignored due to path filters (2)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • frontend/src/assets/calendar-add-icon.svg is excluded by !**/*.svg
📒 Files selected for processing (14)
  • frontend/package.json (1 hunks)
  • frontend/src/components/VerificationModal.module.css (5 hunks)
  • frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (2 hunks)
  • frontend/src/components/attendancemanage/ConfirmationToast.jsx (1 hunks)
  • frontend/src/components/attendancemanage/ConfirmationToast.module.css (1 hunks)
  • frontend/src/components/attendancemanage/RoundDayPicker.jsx (1 hunks)
  • frontend/src/components/attendancemanage/RoundModifyModal.jsx (7 hunks)
  • frontend/src/components/attendancemanage/SessionManagementCard.jsx (4 hunks)
  • frontend/src/components/attendancemanage/SessionManagementCard.module.css (2 hunks)
  • frontend/src/components/attendancemanage/SessionModifyModal.jsx (1 hunks)
  • frontend/src/components/attendancemanage/SessionSettingCard.jsx (2 hunks)
  • frontend/src/contexts/AttendanceContext.jsx (1 hunks)
  • frontend/src/pages/AttendanceManage.jsx (1 hunks)
  • frontend/src/pages/AttendanceManage.module.css (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/pages/AttendanceManage.module.css
  • frontend/src/components/attendancemanage/SessionManagementCard.module.css
  • frontend/package.json
🧰 Additional context used
🧬 Code graph analysis (8)
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (1)
frontend/src/contexts/AttendanceContext.jsx (3)
  • useAttendance (239-245)
  • useAttendance (239-245)
  • handleAttendanceChange (62-75)
frontend/src/components/attendancemanage/RoundModifyModal.jsx (2)
frontend/src/components/attendancemanage/SessionModifyModal.jsx (5)
  • activeToastId (13-13)
  • parseTime (16-23)
  • parseTime (24-24)
  • handleDeleteClick (96-116)
  • handleModifyClick (77-94)
frontend/src/components/attendancemanage/ConfirmationToast.jsx (1)
  • ConfirmationToast (3-26)
frontend/src/pages/AttendanceManage.jsx (7)
frontend/src/components/attendancemanage/RoundDayPicker.jsx (2)
  • useAttendance (10-11)
  • RoundDayPicker (9-99)
frontend/src/contexts/AttendanceContext.jsx (15)
  • useAttendance (239-245)
  • useAttendance (239-245)
  • isRoundModifyModalOpen (56-56)
  • closeRoundModifyModal (109-109)
  • editingRound (55-55)
  • handleRoundChange (77-95)
  • handleDeleteRound (171-189)
  • isSessionModifyModalOpen (58-58)
  • closeSessionModifyModal (112-112)
  • editingSession (57-57)
  • handleSessionChange (97-106)
  • handleDeleteSession (157-170)
  • isAddRoundsModalOpen (60-60)
  • AttendanceProvider (51-236)
  • AttendanceProvider (51-236)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (2)
  • useAttendance (6-17)
  • SessionManagementCard (5-126)
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (2)
  • useAttendance (6-7)
  • AttendanceManagementCard (5-66)
frontend/src/components/attendancemanage/SessionSettingCard.jsx (2)
  • useAttendance (6-6)
  • SessionSettingCard (5-129)
frontend/src/components/attendancemanage/RoundModifyModal.jsx (1)
  • RoundModifyModal (6-197)
frontend/src/components/attendancemanage/SessionModifyModal.jsx (1)
  • SessionModifyModal (6-201)
frontend/src/contexts/AttendanceContext.jsx (5)
frontend/src/components/attendancemanage/SessionSettingCard.jsx (2)
  • sessionTitle (8-8)
  • useAttendance (6-6)
frontend/src/pages/AttendanceManage.jsx (1)
  • useAttendance (17-29)
frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)
  • useAttendance (10-11)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)
  • useAttendance (6-17)
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (1)
  • useAttendance (6-7)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (2)
frontend/src/pages/AttendanceManage.jsx (1)
  • useAttendance (17-29)
frontend/src/contexts/AttendanceContext.jsx (6)
  • useAttendance (239-245)
  • useAttendance (239-245)
  • openAddRoundsModal (114-114)
  • openSessionModifyModal (111-111)
  • selectedRound (54-54)
  • openRoundModifyModal (108-108)
frontend/src/components/attendancemanage/SessionModifyModal.jsx (2)
frontend/src/components/attendancemanage/RoundModifyModal.jsx (9)
  • activeToastId (13-13)
  • parseTime (16-23)
  • parseTime (24-24)
  • hh (26-26)
  • mm (27-27)
  • ss (28-28)
  • isFormValid (53-71)
  • handleModifyClick (73-90)
  • handleDeleteClick (92-112)
frontend/src/components/attendancemanage/ConfirmationToast.jsx (1)
  • ConfirmationToast (3-26)
frontend/src/components/attendancemanage/SessionSettingCard.jsx (1)
frontend/src/contexts/AttendanceContext.jsx (3)
  • useAttendance (239-245)
  • useAttendance (239-245)
  • handleAddSession (118-140)
frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)
frontend/src/contexts/AttendanceContext.jsx (6)
  • useAttendance (239-245)
  • useAttendance (239-245)
  • closeAddRoundsModal (116-116)
  • sessions (52-52)
  • selectedSessionId (53-53)
  • handleAddRounds (141-155)
🔇 Additional comments (10)
frontend/src/components/VerificationModal.module.css (3)

108-109: 버튼 크기 감소의 UI 일관성 확인.

버튼 크기가 132x48에서 100x42로 감소했습니다. 전체 애플리케이션에서 모달 버튼의 일관성과 사용성이 유지되는지 확인하세요. 특히 텍스트 길이가 긴 버튼 라벨이 잘리지 않는지 검증하세요.


138-138: ResetPasswordButton 색상 변경 - 승인.

파괴적 작업(비밀번호 재설정)에 적합한 빨간색(#ff2c4c)으로 변경되었습니다. 비활성화 상태의 색상(#ff9ead)도 적절하게 유지되었습니다.

Also applies to: 141-141


179-183: 새로운 .modifyButtonGroup 클래스 - 승인.

유연한 레이아웃과 버튼 간 일관된 간격을 제공하는 효과적인 추가입니다. 재사용 가능한 컴포넌트 구조에 적합합니다.

frontend/src/contexts/AttendanceContext.jsx (3)

62-75: 출석 상태 업데이트 로직은 방어 코드까지 포함되어 잘 작성되어 있습니다

selectedSessionIdselectedRound로 세션/회차를 찾고, 없으면 조용히 빠져나가는 구조라 NPE 없이 안전하게 동작할 것 같습니다. useImmer 기반으로 참가자 배열을 직접 수정하는 패턴도 이 use-case에 잘 맞습니다.


97-106: 세션 기본 시간/출석 가능 시간 수정 로직은 단순·명확합니다

updateSessionData.id로 세션을 찾고 필요한 필드만 갱신하는 구조라 부작용 범위가 명확하고, 나중에 다른 메타데이터를 추가하기에도 확장성이 좋아 보입니다.


141-155: 여러 회차 추가 후 정렬까지 한 번에 처리하는 구조가 좋습니다

handleAddRounds에서 새 라운드를 한 번에 push한 뒤, 날짜/시간 기준으로 정렬을 다시 해 주는 방식이라, UI에서 항상 시간 순으로 정렬된 목록을 기대하기에 적합해 보입니다. datestartTime을 ISO 형태로 조합해 Date로 비교하는 것도 명확합니다.

frontend/src/components/attendancemanage/SessionSettingCard.jsx (1)

3-7: 세션 생성 핸들러를 컨텍스트에서 가져오는 방향이 API를 단순하게 만들어 줍니다

기존에 상위에서 onAddSession을 props로 내려보내던 구조보다, useAttendance에서 handleAddSession만 끌어다 쓰는 쪽이 이 카드 컴포넌트를 재사용하기에도 더 깔끔해 보입니다. 페이지 레벨에서 세션 추가 로직을 한 곳에서 관리할 수 있다는 점도 좋습니다.

frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)

31-60: 회차 생성 로직(세션 검증 + 로컬 날짜 처리)이 안정적으로 구현되었습니다

currentSession 미존재와 선택된 날짜 0건을 사전에 막고, getTimezoneOffset을 이용해 로컬 기준 YYYY-MM-DD 문자열을 만드는 부분이 실제 운영 시 헷갈리기 쉬운 타임존 문제를 잘 피하고 있습니다.
startTime/availableMinutes를 세션의 디폴트 값에서 가져와 handleAddRounds로 넘기는 것도 컨텍스트 쪽 정렬/관리 로직과 잘 맞습니다.

frontend/src/components/attendancemanage/SessionManagementCard.jsx (2)

5-17: 세션 선택/검증과 회차 추가 모달 오픈 순서가 적절합니다

컨텍스트에서 sessions/selectedSessionId/setSelectedSessionId/openAddRoundsModal 등을 받아와 사용하고, currentSession이 없을 때 회차 추가 버튼 클릭 시 경고를 띄우고 리턴하는 로직으로 이전 리뷰에서 언급됐던 "세션 미선택 상태에서 회차 추가" 문제를 잘 예방하고 있습니다.
세션 셀렉트 변경 시 setSelectedRound(null)로 회차 선택 상태를 초기화하는 부분도 다른 카드(출석 관리)와의 연동을 고려한 합리적인 처리입니다.

Also applies to: 19-40


61-75: 세션/회차 수정 모달을 열기 전 편집 대상 설정 순서가 올바르게 정리되었습니다

세션 수정 버튼에서 currentSession 존재 여부를 먼저 확인한 뒤 setEditingSession(currentSession) → openSessionModifyModal() 순으로 호출하고, 회차 행의 메뉴 버튼에서도 setEditingRound(round)를 호출한 후 openRoundModifyModal()을 실행하는 순서가 잘 맞습니다. e.stopPropagation()으로 행 클릭과 메뉴 클릭 동작을 분리한 것도 의도대로 동작할 것으로 보입니다.
현재 구조라면 모달이 렌더링될 때 항상 올바른 편집 대상이 세팅되어 있어, 이전에 지적되던 편집 대상 race condition 문제는 없어 보입니다.

Also applies to: 87-112

Copy link
Contributor Author

@GamjaIsMine02 GamjaIsMine02 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 감사합니다~!

};
});

handleAddRounds(selectedSessionId, newRounds);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

회차를 추가하기 전에 정렬하면 추가한 뒤에는 다른 회차들까지 봤을 때 정렬되지 않을 수 있으니까 회차 정보가 수정되거나 회차가 추가되었을때 정렬되도록 구현했습니다!

Copy link
Contributor

@gxuoo gxuoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다~!

};
});

handleAddRounds(selectedSessionId, newRounds);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다~ 고생하셨어요!

Copy link
Contributor

@DongEun02 DongEun02 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

react-confirm-alert 한 번 찾아보시는 걸 추천드립니다!

@DongEun02 DongEun02 merged commit 7b5d509 into main Nov 19, 2025
1 check passed
@DongEun02 DongEun02 deleted the attendance-manage-ui-fix branch November 19, 2025 03:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants