Skip to content

[FE] 20260310 fe #294 about attendace manage page#308

Open
sangkyu39 wants to merge 9 commits intomainfrom
20260310-FE-#294-About-Attendace-Manage-Page
Open

[FE] 20260310 fe #294 about attendace manage page#308
sangkyu39 wants to merge 9 commits intomainfrom
20260310-FE-#294-About-Attendace-Manage-Page

Conversation

@sangkyu39
Copy link
Contributor

@sangkyu39 sangkyu39 commented Mar 16, 2026

출석관리 페이지 api 연결
출석 관리 페이지 디자인 적용
기타 오류 수정

Summary by CodeRabbit

Release Notes

  • New Features
    • Sub-board 삭제 기능 추가: 권한이 있는 사용자는 sub-board를 삭제할 수 있습니다.
    • 다중 사용자 선택: 출석 관리에서 여러 사용자를 한번에 선택하고 일괄 작업 가능합니다.
    • 세션 수정 기능: 세션 정보를 편집하고 저장할 수 있습니다.
    • 향상된 일정 관리: 라운드 시간 설정 시 자동으로 종료 시간이 계산됩니다.
    • 삭제 확인 알림: 중요한 작업 삭제 시 확인 알림이 표시됩니다.

Copilot AI review requested due to automatic review settings March 16, 2026 12:40
@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

Walkthrough

참석 관리 UI의 다중 선택 기능 확대, 세션 수정/삭제 모달 추가, sub-board 삭제 기능 도입, 시간 입력 개선을 포함한 광범위한 변경. API 핸들러 및 컨텍스트 로직 업데이트.

Changes

Cohort / File(s) Summary
Board Sub-board 삭제 기능
frontend/src/pages/Board.jsx, frontend/src/components/Board/CategoryTabs.jsx, frontend/src/components/Board/CategoryTabs.module.css, frontend/src/utils/boardApi.js
Sub-board 삭제 권한 및 UI 컨트롤 추가. CategoryTabs에 canDeleteSubBoard, deletingTabId, onDeleteSubBoard props 추가. 탭 호버 시 삭제 버튼 표시. deleteBoard API 함수 신규 추가.
사용자 추가 및 참석 관리
frontend/src/components/attendancemanage/AddUsersModal.jsx, frontend/src/components/attendancemanage/AttendanceManagementCard.jsx
AddUsersModal을 단일 선택에서 다중 선택 테이블 UI로 변경. AttendanceManagementCard를 대규모 리팩토링하여 헤더 액션, 다중 선택, 인라인 상태 변경, 역할 관리 기능 추가.
세션 관리 모달 및 UI
frontend/src/components/attendancemanage/SessionManagementCard.jsx, frontend/src/components/attendancemanage/SessionModifyModal.jsx, frontend/src/components/attendancemanage/SessionSettingCard.jsx
SessionManagementCard에 드롭다운 메뉴, 세션 선택, 세션 수정/삭제 기능 추가. SessionModifyModal 단순화 (삭제 기능 제거, 필드 축소). SessionSettingCard 헤더 아이콘 추가.
라운드 및 시간 입력
frontend/src/components/attendancemanage/RoundDayPicker.jsx
시간 입력 폼 구조 개선. 기본 시간 및 시간 포맷 헬퍼 추가. endTime 자동 계산 로직. 두 열 레이아웃 및 힌트 텍스트 추가.
확인 토스트 및 컨텍스트
frontend/src/components/attendancemanage/ConfirmationToast.jsx, frontend/src/contexts/AttendanceContext.jsx
ConfirmationToast에 title, description, variant props 추가; 역할 변경 시 다른 버튼 배치. AttendanceContext에 handleDeleteUsers, handleAddManager, handleRemoveManager 핸들러 추가.
API 유틸 및 페이지
frontend/src/utils/attendanceManage.js, frontend/src/pages/AttendanceManage.jsx
attendanceManage 유틸에 deleteUser, getUserList, addManager, deleteManager 함수 추가. changeSessionData 시그니처 업데이트. AttendanceManage 페이지에서 SessionModifyModal 통합 제거.
CSS 및 스타일링
frontend/src/components/attendancemanage/*.module.css, frontend/src/components/VerificationModal.module.css, frontend/src/pages/AttendanceManage.module.css
AttendanceManagementCard, ConfirmationToast, SessionManagementCard, SessionSettingCard, AttendanceManage 페이지의 광범위한 CSS 추가 및 리팩토링. 반응형 디자인 개선.
패키지 및 의존성
frontend/package.json
devDependency "baseline-browser-mapping": "^2.10.0" 추가.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60분

Possibly related PRs

Suggested labels

FE, attendance-management, board-management, ui-refactoring

Suggested reviewers

  • DongEun02
  • gxuoo
  • discipline24

Poem

🐰 마우스가 클릭할 때마다,
세션들이 모여 춤을 춘다네,
삭제 버튼 '✕'가 나타났고,
참석 관리는 이제 완벽해,
토끼도 웃음 짓는다 — 호~! 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목이 출석 관리 페이지 관련 변경사항을 언급하지만, 실제 변경 내용은 출석 관리, 서브보드 삭제, 카테고리 탭 등 여러 기능을 포함하고 있어 주요 변경사항을 충분히 반영하지 못함. PR 제목을 더 구체적으로 수정하세요. 예: '[FE] 출석 관리 페이지 API 연결 및 UI 개선' 또는 '[FE] 출석/세션 관리 기능 개선 및 서브보드 삭제 기능 추가'
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 20260310-FE-#294-About-Attendace-Manage-Page
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can enforce grammar and style rules using `languagetool`.

Configure the reviews.tools.languagetool setting to enable/disable rules and categories. Refer to the LanguageTool Community to learn more.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

출석관리 페이지(세션/회차/유저/권한)의 API 연동 및 UI 리디자인을 적용하고, 게시판 하위 게시판 삭제 기능을 추가한 프론트엔드 변경입니다.

Changes:

  • 출석관리 페이지 전반 UI 리뉴얼 및 세션/회차/유저(추가·삭제)/관리자 권한 변경 API 연동
  • 세션/회차 관리 UX 개선(세션 수정 모달, 회차 삭제/세션 삭제 확인 토스트 등)
  • 게시판 하위 게시판 삭제 기능 추가 및 관련 API 유틸 추가

Reviewed changes

Copilot reviewed 22 out of 31 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
frontend/src/utils/boardApi.js 하위 게시판 삭제 API 유틸 추가
frontend/src/utils/axios.js Axios baseURL 설정 변경 및 토큰 재발급 처리 유지
frontend/src/utils/attendanceManage.js 출석 세션/유저/관리자 권한 관련 API 시그니처 및 엔드포인트 갱신
frontend/src/pages/Board.jsx 하위 게시판 삭제 권한/동작 추가 및 탭 컴포넌트로 전달
frontend/src/pages/AttendanceManage.module.css 출석관리 페이지 레이아웃/타이포/입력 UI 정렬 스타일 추가
frontend/src/pages/AttendanceManage.jsx 세션 수정 모달 렌더링 위치 정리/불필요 코드 제거
frontend/src/contexts/AttendanceContext.jsx 출석 상태 변경/세션 수정/유저 삭제/매니저 권한 변경 핸들러 추가 및 로직 수정
frontend/src/components/attendancemanage/SessionSettingCard.module.css 세션 생성 카드 스타일 리디자인 및 정렬
frontend/src/components/attendancemanage/SessionSettingCard.jsx 세션 생성 UI 개선(아이콘/버튼 스타일, 상태 입력 제거)
frontend/src/components/attendancemanage/SessionModifyModal.jsx 세션 수정 모달 UI/필드 구조 변경 및 저장 payload 변경
frontend/src/components/attendancemanage/SessionManagementCard.module.css 세션/회차 테이블 및 메뉴/액션 버튼 스타일 리디자인
frontend/src/components/attendancemanage/SessionManagementCard.jsx 세션 선택/수정/삭제 메뉴 추가, 회차 삭제 확인 토스트 추가
frontend/src/components/attendancemanage/RoundDayPicker.jsx 회차 추가 모달 UI 개선 및 세션 allowedMinutes 기반 종료시간 자동 계산
frontend/src/components/attendancemanage/ConfirmationToast.module.css 확인 토스트 UI 개선 및 roleChange 변형 스타일 추가
frontend/src/components/attendancemanage/ConfirmationToast.jsx 확인 토스트 컴포넌트 확장(라벨/설명/variant)
frontend/src/components/attendancemanage/AttendanceManagementCard.module.css “세션별 유저 관리” 테이블/드롭다운/액션 UI 대폭 리디자인
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx 세션 유저 조회 기반 출석부 렌더링, 상태 드롭다운/일괄 작업/권한 변경 토스트 추가
frontend/src/components/attendancemanage/AddUsersModal.jsx 세션에 추가 가능한 유저 리스트 기반 다중 선택 추가 모달로 변경
frontend/src/components/VerificationModal.module.css 유저 추가/세션 수정/회차 추가 모달 신규 스타일 추가
frontend/src/components/Board/CategoryTabs.module.css 탭 래핑 및 삭제 버튼 스타일 추가
frontend/src/components/Board/CategoryTabs.jsx 하위 게시판 삭제 버튼/로딩 상태/권한 props 추가
frontend/src/assets/x-icon.svg 신규 아이콘 추가(삭제 등)
frontend/src/assets/slash-profile-icon.svg 신규 아이콘 추가(권한 제거)
frontend/src/assets/profile-icon.svg 신규 아이콘 추가(권한 부여)
frontend/src/assets/pencil-icon_2.svg 신규 아이콘 추가(수정)
frontend/src/assets/menu-icon.svg 신규 아이콘 추가(메뉴)
frontend/src/assets/file-icon.svg 신규 아이콘 추가(타이틀/카드)
frontend/src/assets/bin-icon.svg 신규 아이콘 추가(삭제)
frontend/src/assets/add-user-icon.svg 신규 아이콘 추가(유저 추가)
frontend/package.json devDependency 추가(baseline-browser-mapping)
frontend/package-lock.json baseline-browser-mapping 버전/엔트리 갱신
Files not reviewed (1)
  • frontend/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/components/attendancemanage/SessionSettingCard.module.css (1)

11-25: 🛠️ Refactor suggestion | 🟠 Major

중복된 .SessionSettingCardContainer 정의를 통합하세요.

.SessionSettingCardContainer.SessionSettingCardContainer:hover가 두 번 정의되어 있습니다. Lines 303-313의 두 번째 정의가 box-shadownone으로 재정의하여 lines 18-24의 hover 효과를 무효화합니다.

의도적인 디자인 변경이라면 첫 번째 정의의 관련 속성들을 제거하거나, 두 정의를 하나로 통합하세요.

♻️ 통합 제안
 .SessionSettingCardContainer {
+  border: 1px solid `#d7dbe3`;
   border-radius: 12px;
-  background: `#fafbfc`;
+  background: `#f8f9fb`;
   width: 100%;
-  border: 1.5px solid `#d1d5db`;
   flex-shrink: 0;
-  padding: 16px;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
-  transition: box-shadow 0.2s;
+  padding: 20px 16px;
   overflow: hidden;
 }
-
-.SessionSettingCardContainer:hover {
-  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07);
-}

그리고 lines 303-313의 중복 정의를 제거하세요.

Also applies to: 303-313

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/SessionSettingCard.module.css`
around lines 11 - 25, There are duplicate rules for .SessionSettingCardContainer
and .SessionSettingCardContainer:hover where the later block (the one that sets
box-shadow: none) overrides the intended hover effect; locate the two
occurrences of .SessionSettingCardContainer and
.SessionSettingCardContainer:hover, remove the redundant/contradicting second
definition (or merge them) so the hover rule preserves the intended box-shadow
(keep the hover box-shadow declaration from the first set), and ensure any
deliberate design changes are applied by explicitly updating the single
consolidated .SessionSettingCardContainer and .SessionSettingCardContainer:hover
blocks.
frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)

58-73: ⚠️ Potential issue | 🟠 Major

세션을 바꿀 때 이전 회차 목록이 남고 늦은 응답이 덮어쓸 수 있습니다.

이 effect는 selectedSessionId가 바뀌어도 기존 currentDisplayedRounds를 바로 지우지 않고, 이전 getRounds 응답도 그대로 반영합니다. 빠르게 세션을 전환하면 QR 생성/삭제 대상이 다른 세션 회차로 어긋날 수 있습니다.

🔧 제안 수정안
+ useEffect(() => {
+   setCurrentDisplayedRounds([]);
+ }, [selectedSessionId]);
+
  useEffect(() => {
+   let cancelled = false;
+
    const fetchRounds = async () => {
      if (!selectedSessionId) {
-       setCurrentDisplayedRounds([]);
        return;
      }
      try {
        const rounds = await getRounds(selectedSessionId);
-       setCurrentDisplayedRounds(rounds || []);
+       if (!cancelled) {
+         setCurrentDisplayedRounds(rounds || []);
+       }
      } catch (e) {
-       toast.error('라운드를 불러오지 못했습니다.');
-       setCurrentDisplayedRounds([]);
+       if (!cancelled) {
+         toast.error('라운드를 불러오지 못했습니다.');
+         setCurrentDisplayedRounds([]);
+       }
      }
    };
    fetchRounds();
+
+   return () => {
+     cancelled = true;
+   };
  }, [selectedSessionId, roundsVersion]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/SessionManagementCard.jsx` around
lines 58 - 73, When selectedSessionId changes the old async getRounds can
resolve later and overwrite the new session's rounds; in the useEffect around
fetchRounds (and the dependency on selectedSessionId/roundsVersion) clear
currentDisplayedRounds immediately when starting a new fetch and ignore stale
responses by using a local requestId (or AbortController if getRounds supports
it): increment a token before calling getRounds, capture it in the closure, and
only call setCurrentDisplayedRounds(rounds) if the token matches; also ensure
the early-return branch for no selectedSessionId still clears state immediately
and that catch does the same.
🧹 Nitpick comments (9)
frontend/src/components/attendancemanage/RoundDayPicker.jsx (2)

58-70: endTime 갱신 경로를 하나로 정리하는 게 좋습니다.

handleStartTimeChangeuseEffect가 동일 계산으로 setEndTime를 호출해 중복 업데이트가 발생합니다. 한 경로만 유지하면 렌더링/로직 추적이 단순해집니다.

리팩터 예시 (effect 단일화)
   const handleStartTimeChange = (e) => {
     const nextStartTime = e.target.value;
     setStartTime(nextStartTime);
-
-    if (allowedMinutes > 0) {
-      setEndTime(calculateEndTime(nextStartTime, allowedMinutes));
-    }
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/RoundDayPicker.jsx` around lines 58
- 70, The code currently updates endTime in both handleStartTimeChange and the
useEffect, causing duplicate updates; remove the setEndTime call from
handleStartTimeChange so that handleStartTimeChange only calls
setStartTime(nextStartTime), and let the existing useEffect ([startTime,
allowedMinutes]) perform setEndTime(calculateEndTime(startTime, allowedMinutes))
when startTime or allowedMinutes change (keeping the guard for allowedMinutes >
0 and startTime truthiness).

105-105: 운영 코드에서는 디버그 console.log 제거를 권장합니다.

성공 경로 로그는 콘솔 노이즈를 늘려 실제 장애 로그 탐지를 방해할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/RoundDayPicker.jsx` at line 105,
Remove the development console.log in RoundDayPicker.jsx that prints "새로운 라운드
데이터:" and newRound; either delete the line entirely or replace it with a proper
debug-level logger call (e.g., logger.debug) or guard it behind an environment
check (e.g., if (process.env.NODE_ENV !== 'production') { console.log(...) }) so
production builds do not emit noisy console output.
frontend/src/components/VerificationModal.module.css (1)

313-326: 공통 버튼 스타일을 기본 클래스로 추출하여 유지보수성을 개선합니다.

취소 버튼(.cancelButton, .sessionEditCancelButton, .roundAddCancelButton)과 확인 버튼(.sessionEditSubmitButton, .roundAddSubmitButton)이 거의 동일한 스타일을 반복하고 있습니다. 높이(46px), 테두리 반경(10px), 폰트 크기, 폰트 무게(600) 등이 중복되며, 색상과 테두리만 변형하고 있습니다.

공통 기본 클래스(예: .baseButton)를 만들고 취소/확인별 변형 클래스(.baseButton--cancel, .baseButton--submit)로 분리하면, 앞으로 버튼 스타일 수정 시 한 곳만 변경하면 되므로 불일치 발생을 방지할 수 있습니다.

해당 클래스 위치
  • 313-326: .cancelButton
  • 432-451: .sessionEditActionButton, .sessionEditCancelButton, .sessionEditSubmitButton
  • 573-597: .roundAddCancelButton, .roundAddSubmitButton
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/VerificationModal.module.css` around lines 313 - 326,
Extract the repeated button styles into a shared .baseButton class containing
common rules (height:46px, border-radius:10px, font-size:16px, font-weight:600,
width where applicable) and create modifier classes .baseButton--cancel and
.baseButton--submit for the color/border/background differences; update
.cancelButton, .sessionEditCancelButton, .roundAddCancelButton to use
.baseButton + .baseButton--cancel and update .sessionEditSubmitButton,
.roundAddSubmitButton (and .sessionEditActionButton if applicable) to use
.baseButton + .baseButton--submit; remove duplicated properties from the
specific classes so only the modifiers contain the unique
border/background/color rules while all other shared styles live in .baseButton.
frontend/src/components/attendancemanage/SessionSettingCard.module.css (4)

67-73: 중복된 .availableTimeInputGroup 정의

동일 클래스가 두 번 정의되어 있습니다. 최종 의도된 스타일 (lines 334-337)만 유지하고, 충돌하는 첫 번째 정의 (lines 67-73)를 조정하세요.

Also applies to: 334-337

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/SessionSettingCard.module.css`
around lines 67 - 73, Duplicate CSS class .availableTimeInputGroup exists;
remove or merge the earlier/first definition so only the intended final
.availableTimeInputGroup definition remains. Locate occurrences of
.availableTimeInputGroup in SessionSettingCard.module.css, delete the
conflicting/older rule (the one with grid-template-columns: 1fr 1fr; gap: 8px;
align-items: center) or consolidate its properties into the final rule so there
is a single authoritative .availableTimeInputGroup block with the intended
styles.

27-32: 중복된 .form 클래스 정의 통합 필요

.form 클래스가 두 번 정의되어 있습니다 (lines 27-32, 327-332). 두 번째 정의에서 grid-template-columns1fr에서 1.3fr 1.3fr 0.7fr auto로 변경되고 align-items: end가 추가됩니다.

의도한 레이아웃이 lines 327-332라면, 첫 번째 정의를 업데이트하고 중복을 제거하세요.

Also applies to: 327-332

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/SessionSettingCard.module.css`
around lines 27 - 32, There are two duplicate `.form` class definitions; remove
the redundant one and merge so only a single `.form` exists with the intended
layout. Update the `.form` definition to use the final grid settings
(`grid-template-columns: 1.3fr 1.3fr 0.7fr auto; gap: 14px; width: 100%;
display: grid; align-items: end;`) and delete the other `.form` block to avoid
override/conflict.

138-174: 반응형 미디어 쿼리 간 불일치

Lines 138-271의 미디어 쿼리는 min-width (모바일 퍼스트)를 사용하고, lines 380-405의 미디어 쿼리는 max-width (데스크톱 퍼스트)를 사용합니다. 이 혼합된 접근 방식은 예측하기 어려운 캐스케이딩 결과를 초래할 수 있습니다.

일관된 미디어 쿼리 전략 (모바일 퍼스트 또는 데스크톱 퍼스트)을 선택하는 것을 권장합니다.

Also applies to: 176-209, 211-244, 246-271, 380-405

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/SessionSettingCard.module.css`
around lines 138 - 174, The CSS uses mixed responsive strategies—some media
queries use min-width (mobile-first) and others use max-width (desktop-first),
causing unpredictable cascade; pick one strategy and make the other set
consistent (e.g., convert all max-width queries to min-width) and update the
affected selectors (.SessionSettingCardContainer, .form, .timeInputGroup,
.availableTimeInputGroup and their children) so their breakpoints and rule
specificity follow the chosen approach across the stylesheet (ensure spacing,
font-size and padding overrides are moved into the matching min-width queries or
inverted logic as needed).

1-3: 전역 * 선택자 범위 확인

* { box-sizing: border-box; }가 CSS 모듈 파일에 있지만, CSS 모듈은 기본적으로 클래스 스코핑만 제공합니다. 이 전역 선택자는 다른 컴포넌트에 의도치 않게 영향을 줄 수 있습니다.

전역 스타일은 별도의 글로벌 CSS 파일에서 관리하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/SessionSettingCard.module.css`
around lines 1 - 3, The global universal selector "* { box-sizing: border-box;
}" in the CSS module should not live inside the module because it can leak to
other components; remove that rule from SessionSettingCard.module.css and either
(a) move it into your centralized global stylesheet (e.g., index.css /
global.css) so box-sizing is applied app-wide, or (b) if you truly need it
scoped, wrap it with the CSS module global wrapper like :global(*) { box-sizing:
border-box; } — update imports accordingly so the global rule is applied from
the appropriate global file instead of inside the module.
frontend/src/components/attendancemanage/SessionManagementCard.module.css (1)

468-594: 동일 선택자 중복 재정의가 많아 스타일 충돌 리스크가 큽니다.

Line 468 이후가 상단 정의를 광범위하게 다시 덮어쓰고 있어, 브레이크포인트 경계에서 예상치 못한 우선순위 충돌이 발생하기 쉽습니다. 베이스 스타일과 디자인 오버라이드를 분리하거나, 한 번만 정의하도록 정리하는 편이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/SessionManagementCard.module.css`
around lines 468 - 594, The CSS file has many overlapping selector redefinitions
(e.g., .sessionManagementCardContainer, .sessionSelect, .table thead th, .table
tbody td) causing priority/conflict risk at breakpoints; refactor by extracting
base styles for those classes into a single block (keep
.sessionManagementCardContainer, .sessionSelect, .table thead th, .table tbody
td as the canonical base) and move breakpoint-specific overrides into minimal,
focused media-query rules that only change the needed properties (e.g.,
font-size, padding, min-width), removing duplicate declarations and ensuring no
broad redefinitions shadow the base rules.
frontend/src/pages/AttendanceManage.module.css (1)

400-546: 같은 selector를 두 번 정의해 cascade가 이미 너무 취약합니다.

앞쪽 기본/반응형 규칙을 그대로 둔 채 동일 class를 뒤에서 다시 전부 덮어써서, 기존 min-width 블록 상당수가 사실상 죽었습니다. 새 디자인이 기준이면 이전 블록을 제거하고 한 벌만 남겨두는 편이 이후 breakpoint 수정 때 훨씬 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/AttendanceManage.module.css` around lines 400 - 546, You
have duplicate selector definitions (e.g., .mainTitle, .header, .buttonGroup,
.inputGroup .label, .inputGroup input) declared both in the main block and again
inside the media queries which overwrites earlier rules and weakens the cascade;
fix by consolidating into a single canonical definition for each selector (keep
the preferred styles — if the new design is authoritative remove the older
conflicting declarations) and use the `@media` blocks only to override the
specific properties that must change at breakpoints
(height/font-size/margins/etc.), ensuring you remove the redundant full
re-declarations so future breakpoint edits are safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/attendancemanage/AddUsersModal.jsx`:
- Around line 52-59: The try/catch in AddUsersModal.jsx cannot detect failures
because handleAddUsers in AttendanceContext.jsx (the function around lines
223-230) swallows errors instead of rejecting; update the contract so failures
propagate: either modify handleAddUsers to rethrow the caught error (or return a
rejected Promise) when the add fails, or change its return value to a clear
success/failure boolean and have AddUsersModal.jsx check that result for each
user and only call closeAddUsersModal() if all adds succeed; ensure references
to handleAddUsers and selectedSessionId are used so AddUsersModal can reliably
detect and react to failures.

In `@frontend/src/components/attendancemanage/AttendanceManagementCard.jsx`:
- Around line 125-142: The effect in AttendanceManagementCard (useEffect ->
fetchAttendanceSheet) doesn't clear attendance state immediately and can suffer
from race conditions when selectedSessionId changes quickly; on session change
immediately call setAttendanceData({ sessionTitle: '', rounds: [], userRows: []
}) and setSelectedUserIds(new Set()) before issuing the async
getUsers(selectedSessionId) request, and guard the async response so stale
responses don't overwrite state (e.g., use an AbortController or a local
requestId/version token checked before calling setAttendanceData and
setSelectedUserIds in the try/catch). Ensure the cleanup/abort is wired so only
the latest fetch updates state in fetchAttendanceSheet and stale promises are
ignored.
- Around line 155-179: confirmAction and confirmRoleChangeAction render
ConfirmationToast without providing the per-toast close callback so the cancel
button is nonfunctional and calling toast.dismiss() closes all toasts; change
both functions (confirmAction and confirmRoleChangeAction) to use the
toast(renderCallback, options) form that receives closeToast (e.g.
toast((closeToast) => <ConfirmationToast onConfirm={...} onCancel={() =>
closeToast()} message={...} /> , { autoClose:false, closeOnClick:false,
draggable:false, closeButton:false, onClose:()=>setActiveToastId(null) })),
remove any global toast.dismiss() calls inside the toast UI handlers and instead
call closeToast() to only close the specific toast, and still clear
selectedUserIds and update setActiveToastId(toastId) as before so the active
toast tracking remains correct.

In
`@frontend/src/components/attendancemanage/AttendanceManagementCard.module.css`:
- Around line 162-173: The stylelint error comes from not having an empty line
after the custom property block in the .attendanceSelectWrap rule; open the
.attendanceSelectWrap rule and insert a blank line immediately after the last
custom property (--status-bg-hover: `#deeeff`;) so there is an empty line before
the next declaration (position: relative;), keeping all existing properties and
values unchanged.

In `@frontend/src/components/attendancemanage/ConfirmationToast.module.css`:
- Around line 98-104: The mobile min-width set on .button (min-width: 110px) is
being overridden by the role-change variant (roleConfirmButton /
.roleConfirmButton) which applies min-width: 148px, causing horizontal overflow
on small viewports; update the role-change variant to not force a larger
min-width on small screens — either remove the hardcoded min-width there,
replace it with a responsive rule (e.g., a media query that reduces or unsets
min-width below a breakpoint), or let the variant inherit .button’s min-width
(or use min-width: 110px / unset) so both buttons can shrink and prevent
overflow.
- Around line 107-115: The .roleConfirmButton color (`#2f80ed`) fails WCAG AA
contrast for white text; update the background-color and border-color in the
.roleConfirmButton rule to a darker blue that achieves at least 4.5:1 contrast
(for example replace `#2f80ed` with a darker hex such as the hover value `#1f6fdc`
or an even darker blue that you verify), keep color: `#fff`, and keep or adjust
.roleConfirmButton:hover to an appropriately darker shade to preserve hover
differentiation; verify the chosen hex with a contrast checker after changing
the background-color and border-color entries.

In `@frontend/src/components/attendancemanage/RoundDayPicker.jsx`:
- Around line 34-43: calculateEndTime currently wraps minutes modulo 24 hours so
times past midnight appear earlier than startAt; change calculateEndTime to
compute using a Date (or accept a base date) so it returns a datetime-aware
result (e.g., formatted time plus a nextDay flag or an ISO timestamp) by
creating a Date from baseTime + minutesToAdd and checking if the resulting day
differs from the base day; then update the code that sets closeAt (the caller at
Line 92–93) to use that datetime/nextDay information and, when the end datetime
is on the next day (end <= start), set closeAt to the next-day value (or include
the next-day marker) instead of a same-day time so the range does not become
reversed.

In `@frontend/src/components/attendancemanage/SessionModifyModal.jsx`:
- Around line 22-37: The modal currently calls onClose immediately after onSave
in handleModifyClick, which hides failures; make handleModifyClick async, await
the promise returned by onSave(session.sessionId, {...}), and only call onClose
when that await succeeds; wrap the await in try/catch and on error show a
user-facing error (e.g., alert or error handler) and do not close the modal;
keep the existing allowedMinutes validation and the payload shape (title,
description, allowedMinutes, status) intact.

In `@frontend/src/components/attendancemanage/SessionSettingCard.module.css`:
- Around line 273-300: There are two duplicate .createButton class blocks;
remove the earlier .createButton definition and keep the later one (or merge any
differing properties from the first into the second) so only a single
.createButton rule remains; update any references if needed to ensure styles
remain identical by consolidating properties into the retained .createButton
block.

In `@frontend/src/components/Board/CategoryTabs.module.css`:
- Around line 57-83: The delete button is only revealed via hover/focus-within
(selectors .tabItem:hover .deleteTabButton and .tabItem:focus-within
.deleteTabButton), which hides it on touch devices; fix by adding touch-friendly
rules—e.g. include .tabItem:active .deleteTabButton and .tabItem:focus
.deleteTabButton, and add a media query for touch devices (`@media` (hover: none)
and (pointer: coarse)) to set .deleteTabButton { opacity: 1; pointer-events:
auto; } so the button is accessible on mobile without relying on hover.

In `@frontend/src/components/VerificationModal.module.css`:
- Around line 486-488: Stylelint is flagging the CSS Modules :global()
pseudo-class (used in the .roundAddDayPicker :global(.rdp) rule) as an unknown
pseudo-class; update the Stylelint config (e.g., .stylelintrc.json) to allow CSS
Modules pseudo-classes by adjusting the selector-pseudo-class-no-unknown rule to
ignore "global" and "local" or extend a CSS Modules-aware config (for example,
add the ignorePseudoClasses entry for "global" and "local" or extend
stylelint-config-css-modules) so the :global(...) usage no longer triggers the
linter.

In `@frontend/src/contexts/AttendanceContext.jsx`:
- Around line 232-269: The three handlers (handleDeleteUsers, handleAddManager,
handleRemoveManager) currently use Promise.all which aborts on the first
rejection and prevents setRoundAttendanceVersion from running when some requests
succeeded; change each to use Promise.allSettled on the mapped calls to
deleteUser/addManager/deleteManager, then after awaiting results call
setRoundAttendanceVersion((v) => v + 1) regardless of failures and compute
success/failure from the settled results to determine whether to log or show an
alert about partial failures (e.g., count rejected results and include that info
in console.error/alert).

In `@frontend/src/utils/attendanceManage.js`:
- Around line 146-148: The error log in the catch block currently prints "세션 삭제
중 오류 발생" which is misleading because this catch handles the user deletion API
failure; update the console.error message in the catch that references err (the
block using console.error('세션 삭제 중 오류 발생', err)) to accurately reflect the
failure (e.g., "유저 삭제 API 실패" or similar) and keep the thrown err behavior
unchanged so the error context remains available for debugging.

In `@frontend/src/utils/axios.js`:
- Around line 2-4: BASE_URL is computed but not used: change the axios instance
creation to set baseURL to the previously computed BASE_URL (replace baseURL: ''
with baseURL: BASE_URL in the api created by axios.create) and ensure any manual
token refresh calls (the refresh / token renewal request referenced around the
token refresh logic) use the same api instance or explicitly use BASE_URL so
both regular requests and token refresh requests target the same origin; update
references to direct '' or hardcoded URLs to use BASE_URL or the api instance.

---

Outside diff comments:
In `@frontend/src/components/attendancemanage/SessionManagementCard.jsx`:
- Around line 58-73: When selectedSessionId changes the old async getRounds can
resolve later and overwrite the new session's rounds; in the useEffect around
fetchRounds (and the dependency on selectedSessionId/roundsVersion) clear
currentDisplayedRounds immediately when starting a new fetch and ignore stale
responses by using a local requestId (or AbortController if getRounds supports
it): increment a token before calling getRounds, capture it in the closure, and
only call setCurrentDisplayedRounds(rounds) if the token matches; also ensure
the early-return branch for no selectedSessionId still clears state immediately
and that catch does the same.

In `@frontend/src/components/attendancemanage/SessionSettingCard.module.css`:
- Around line 11-25: There are duplicate rules for .SessionSettingCardContainer
and .SessionSettingCardContainer:hover where the later block (the one that sets
box-shadow: none) overrides the intended hover effect; locate the two
occurrences of .SessionSettingCardContainer and
.SessionSettingCardContainer:hover, remove the redundant/contradicting second
definition (or merge them) so the hover rule preserves the intended box-shadow
(keep the hover box-shadow declaration from the first set), and ensure any
deliberate design changes are applied by explicitly updating the single
consolidated .SessionSettingCardContainer and .SessionSettingCardContainer:hover
blocks.

---

Nitpick comments:
In `@frontend/src/components/attendancemanage/RoundDayPicker.jsx`:
- Around line 58-70: The code currently updates endTime in both
handleStartTimeChange and the useEffect, causing duplicate updates; remove the
setEndTime call from handleStartTimeChange so that handleStartTimeChange only
calls setStartTime(nextStartTime), and let the existing useEffect ([startTime,
allowedMinutes]) perform setEndTime(calculateEndTime(startTime, allowedMinutes))
when startTime or allowedMinutes change (keeping the guard for allowedMinutes >
0 and startTime truthiness).
- Line 105: Remove the development console.log in RoundDayPicker.jsx that prints
"새로운 라운드 데이터:" and newRound; either delete the line entirely or replace it with
a proper debug-level logger call (e.g., logger.debug) or guard it behind an
environment check (e.g., if (process.env.NODE_ENV !== 'production') {
console.log(...) }) so production builds do not emit noisy console output.

In `@frontend/src/components/attendancemanage/SessionManagementCard.module.css`:
- Around line 468-594: The CSS file has many overlapping selector redefinitions
(e.g., .sessionManagementCardContainer, .sessionSelect, .table thead th, .table
tbody td) causing priority/conflict risk at breakpoints; refactor by extracting
base styles for those classes into a single block (keep
.sessionManagementCardContainer, .sessionSelect, .table thead th, .table tbody
td as the canonical base) and move breakpoint-specific overrides into minimal,
focused media-query rules that only change the needed properties (e.g.,
font-size, padding, min-width), removing duplicate declarations and ensuring no
broad redefinitions shadow the base rules.

In `@frontend/src/components/attendancemanage/SessionSettingCard.module.css`:
- Around line 67-73: Duplicate CSS class .availableTimeInputGroup exists; remove
or merge the earlier/first definition so only the intended final
.availableTimeInputGroup definition remains. Locate occurrences of
.availableTimeInputGroup in SessionSettingCard.module.css, delete the
conflicting/older rule (the one with grid-template-columns: 1fr 1fr; gap: 8px;
align-items: center) or consolidate its properties into the final rule so there
is a single authoritative .availableTimeInputGroup block with the intended
styles.
- Around line 27-32: There are two duplicate `.form` class definitions; remove
the redundant one and merge so only a single `.form` exists with the intended
layout. Update the `.form` definition to use the final grid settings
(`grid-template-columns: 1.3fr 1.3fr 0.7fr auto; gap: 14px; width: 100%;
display: grid; align-items: end;`) and delete the other `.form` block to avoid
override/conflict.
- Around line 138-174: The CSS uses mixed responsive strategies—some media
queries use min-width (mobile-first) and others use max-width (desktop-first),
causing unpredictable cascade; pick one strategy and make the other set
consistent (e.g., convert all max-width queries to min-width) and update the
affected selectors (.SessionSettingCardContainer, .form, .timeInputGroup,
.availableTimeInputGroup and their children) so their breakpoints and rule
specificity follow the chosen approach across the stylesheet (ensure spacing,
font-size and padding overrides are moved into the matching min-width queries or
inverted logic as needed).
- Around line 1-3: The global universal selector "* { box-sizing: border-box; }"
in the CSS module should not live inside the module because it can leak to other
components; remove that rule from SessionSettingCard.module.css and either (a)
move it into your centralized global stylesheet (e.g., index.css / global.css)
so box-sizing is applied app-wide, or (b) if you truly need it scoped, wrap it
with the CSS module global wrapper like :global(*) { box-sizing: border-box; } —
update imports accordingly so the global rule is applied from the appropriate
global file instead of inside the module.

In `@frontend/src/components/VerificationModal.module.css`:
- Around line 313-326: Extract the repeated button styles into a shared
.baseButton class containing common rules (height:46px, border-radius:10px,
font-size:16px, font-weight:600, width where applicable) and create modifier
classes .baseButton--cancel and .baseButton--submit for the
color/border/background differences; update .cancelButton,
.sessionEditCancelButton, .roundAddCancelButton to use .baseButton +
.baseButton--cancel and update .sessionEditSubmitButton, .roundAddSubmitButton
(and .sessionEditActionButton if applicable) to use .baseButton +
.baseButton--submit; remove duplicated properties from the specific classes so
only the modifiers contain the unique border/background/color rules while all
other shared styles live in .baseButton.

In `@frontend/src/pages/AttendanceManage.module.css`:
- Around line 400-546: You have duplicate selector definitions (e.g.,
.mainTitle, .header, .buttonGroup, .inputGroup .label, .inputGroup input)
declared both in the main block and again inside the media queries which
overwrites earlier rules and weakens the cascade; fix by consolidating into a
single canonical definition for each selector (keep the preferred styles — if
the new design is authoritative remove the older conflicting declarations) and
use the `@media` blocks only to override the specific properties that must change
at breakpoints (height/font-size/margins/etc.), ensuring you remove the
redundant full re-declarations so future breakpoint edits are safe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a3b9379f-53b9-4ea8-bda1-1d9039092823

📥 Commits

Reviewing files that changed from the base of the PR and between f8b1f21 and fe502c2.

⛔ Files ignored due to path filters (9)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • frontend/src/assets/add-user-icon.svg is excluded by !**/*.svg
  • frontend/src/assets/bin-icon.svg is excluded by !**/*.svg
  • frontend/src/assets/file-icon.svg is excluded by !**/*.svg
  • frontend/src/assets/menu-icon.svg is excluded by !**/*.svg
  • frontend/src/assets/pencil-icon_2.svg is excluded by !**/*.svg
  • frontend/src/assets/profile-icon.svg is excluded by !**/*.svg
  • frontend/src/assets/slash-profile-icon.svg is excluded by !**/*.svg
  • frontend/src/assets/x-icon.svg is excluded by !**/*.svg
📒 Files selected for processing (22)
  • frontend/package.json
  • frontend/src/components/Board/CategoryTabs.jsx
  • frontend/src/components/Board/CategoryTabs.module.css
  • frontend/src/components/VerificationModal.module.css
  • frontend/src/components/attendancemanage/AddUsersModal.jsx
  • frontend/src/components/attendancemanage/AttendanceManagementCard.jsx
  • frontend/src/components/attendancemanage/AttendanceManagementCard.module.css
  • frontend/src/components/attendancemanage/ConfirmationToast.jsx
  • frontend/src/components/attendancemanage/ConfirmationToast.module.css
  • frontend/src/components/attendancemanage/RoundDayPicker.jsx
  • frontend/src/components/attendancemanage/SessionManagementCard.jsx
  • frontend/src/components/attendancemanage/SessionManagementCard.module.css
  • frontend/src/components/attendancemanage/SessionModifyModal.jsx
  • frontend/src/components/attendancemanage/SessionSettingCard.jsx
  • frontend/src/components/attendancemanage/SessionSettingCard.module.css
  • frontend/src/contexts/AttendanceContext.jsx
  • frontend/src/pages/AttendanceManage.jsx
  • frontend/src/pages/AttendanceManage.module.css
  • frontend/src/pages/Board.jsx
  • frontend/src/utils/attendanceManage.js
  • frontend/src/utils/axios.js
  • frontend/src/utils/boardApi.js

@sangkyu39 sangkyu39 requested a review from discipline24 as a code owner March 16, 2026 14:20
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

♻️ Duplicate comments (2)
frontend/src/components/attendancemanage/RoundDayPicker.jsx (1)

146-150: ⚠️ Potential issue | 🟠 Major

try/catch는 현재 계약으로는 실패를 잡지 못합니다.

frontend/src/contexts/AttendanceContext.jsxhandleAddRounds는 실패 시 로그만 남기고 resolve하므로, 여기서는 API 실패가 나도 closeAddRoundsModal()까지 실행됩니다. 성공 여부를 반환받거나 컨텍스트에서 에러를 다시 던지도록 맞추지 않으면 모달이 실패 상황에서도 닫힙니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/RoundDayPicker.jsx` around lines 146
- 150, The try/catch in RoundDayPicker.jsx cannot detect failures because
AttendanceContext.jsx's handleAddRounds swallows errors and resolves; either
change handleAddRounds to propagate errors (rethrow the caught error) or make it
return a success flag (e.g., boolean) so callers can branch; then update
RoundDayPicker.jsx to await handleAddRounds and only call closeAddRoundsModal()
when the call returns success or does not throw, and handle/display the error in
the catch branch. Reference the handleAddRounds function in
AttendanceContext.jsx and the closeAddRoundsModal call in RoundDayPicker.jsx
when making the changes.
frontend/src/components/attendancemanage/AddUsersModal.jsx (1)

12-30: ⚠️ Potential issue | 🟠 Major

세션이 바뀔 때 이전 사용자 목록과 선택 상태를 즉시 비워 주세요.

Line 12-30은 새 요청을 보내기 전에 users/selectedUserIds를 초기화하지 않고, 늦게 도착한 이전 요청도 막지 않습니다. 그래서 모달이 다른 세션의 사용자 목록을 잠깐 보여 주거나, 이전 응답이 현재 세션 목록을 덮어쓸 수 있습니다.

🔧 제안 수정안
 useEffect(() => {
+  let cancelled = false;
+  setUsers([]);
+  setSelectedUserIds(new Set());
+
   const fetchUsers = async () => {
     try {
       const userList = await getUserList(selectedSessionId);
-      setUsers(userList);
+      if (!cancelled) setUsers(userList);
     } catch (err) {
       console.error('사용자 목록을 불러오는 데 실패했습니다:', err);
     }
   };
   if (selectedSessionId) {
     fetchUsers();
   }
@@
-  return () => document.removeEventListener('keydown', handleKeyDown);
+  return () => {
+    cancelled = true;
+    document.removeEventListener('keydown', handleKeyDown);
+  };
 }, [selectedSessionId, closeAddUsersModal]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/AddUsersModal.jsx` around lines 12 -
30, When selectedSessionId changes we must immediately clear previous
users/selection and prevent stale responses from overwriting new state: inside
the useEffect that defines fetchUsers (the effect watching selectedSessionId and
closeAddUsersModal) call setUsers([]) and setSelectedUserIds([]) right away when
selectedSessionId is truthy, then make the fetch cancellable and/or guarded —
create an AbortController and pass its signal into getUserList (or, if
getUserList cannot accept a signal, generate a local requestId/sessionToken
captured by the async response and only call setUsers when the requestId still
matches the latest selectedSessionId); also abort the controller in the effect
cleanup to prevent late responses from updating state; keep handleKeyDown and
its add/remove as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/attendancemanage/AddUsersModal.jsx`:
- Around line 57-75: When partial adds occur (results from Promise.allSettled),
you must remove successfully-added users from the UI state and keep only the
failed IDs selected so retries don't resend already-added users; after computing
results from idsArray and handleAddUsers, derive failedIds =
results.map((r,i)=>({r,i})).filter(r=>r.r.status==='rejected').map(r=>idsArray[r.i])
then update the relevant state: remove successful IDs from users (e.g. update
users/state used in the modal) and set selectedUserIds to failedIds, and log the
failedIds/details; keep the early-close path (failedCount === 0 ->
closeAddUsersModal()) but when some failed, do not close modal — instead update
users and selectedUserIds to only show/keep failures so retries only reattempt
failed IDs and the UI identifies which items failed.

In `@frontend/src/components/attendancemanage/AttendanceManagementCard.jsx`:
- Around line 132-162: When the session changes you must clear any open
confirmation toasts so their onConfirm closures don't operate on stale
selectedSessionId/selectedUserIds; in the useEffect that resets selection (the
block that calls setAttendanceData and setSelectedUserIds) call the toast
dismissal/cleanup used by confirmAction/confirmRoleChangeAction (close
activeToastId or call the toast dismiss helper) before resetting selection, and
ensure confirmAction/confirmRoleChangeAction read current
selectedSessionId/selectedUserIds (or are re-created) so they don't capture old
values; update code around fetchRequestIdRef/useEffect to explicitly clear
activeToastId when selectedSessionId changes.
- Around line 182-186: The current onConfirm handler always clears
selectedUserIds after awaiting onConfirm, which loses failed targets when
underlying actions (handleDeleteUsers / handleAddManager / handleRemoveManager)
perform partial failures without rejecting; change the flow so you only clear
successful IDs: update onConfirm (or its callers) to return a result summary
(e.g., { failedIds: string[] } or a success boolean), then in the onConfirm
caller check that result—if result.failedIds is empty (or success === true)
clear the selection and closeToast, otherwise retain failed IDs in
selectedUserIds (e.g., setSelectedUserIds(new Set(result.failedIds))) so the
user can retry; alternatively, if changing onConfirm is not possible, wrap the
call in a try/catch and only clear selection on explicit success responses from
the functions handleDeleteUsers / handleAddManager / handleRemoveManager.

In
`@frontend/src/components/attendancemanage/AttendanceManagementCard.module.css`:
- Around line 584-588: The .tableGroup CSS rule is overriding the outer scroll
and preventing access to dynamically added "회차" columns on narrow screens;
change .tableGroup to allow horizontal scrolling (e.g., restore overflow-x:auto
or overflow:auto instead of overflow:visible) so the table wrapper can scroll
while keeping any dropdowns’ overflow visible at the inner element level, and
ensure the AttendanceManagementCard component (the table wrapper where min-width
is fixed to 920px) remains scrollable so columns added in
AttendanceManagementCard.jsx (the dynamic column generation around lines
~377-390) remain reachable on small screens.

In `@frontend/src/components/attendancemanage/RoundDayPicker.jsx`:
- Around line 129-136: The code in RoundDayPicker.jsx currently advances
endDateTime to the next day whenever endDateTime <= startDateTime, which
converts simple input mistakes into 24-hour shifts; change this so you only roll
the end time forward when the user explicitly chose a next-day option (e.g., a
boolean like nextDaySelected/rolloverNextDay), otherwise do not mutate
endDateTime and instead surface a validation error (prevent save) when
endDateTime <= startDateTime; update the logic around
startDateTime/endDateTime/closeAt to check that explicit flag before calling
endDateTime.setDate(...) and add or reuse a UI control/state (nextDaySelected)
to let users indicate next-day end times.

In `@frontend/src/components/Board/CategoryTabs.module.css`:
- Around line 98-102: The disabled styles on .deleteTabButton currently apply
globally and make all delete buttons visible when deletingTabId disables them;
change the rule so the visual disabled treatment (opacity: 0.75 and
pointer-events: auto) only applies when the button is actually exposed/visible
(e.g., scoped to a visibility modifier such as .deleteTabButton.visible:disabled
or .deleteTabButton[data-visible="true"]:disabled) and keep the default disabled
behavior otherwise (restore pointer-events to none for truly hidden/disabled
buttons). Update the CSS selector that targets .deleteTabButton:disabled to be
conditional on the visible state and ensure any JS that toggles deletingTabId
also sets that visibility class/attribute on the specific button(s).

---

Duplicate comments:
In `@frontend/src/components/attendancemanage/AddUsersModal.jsx`:
- Around line 12-30: When selectedSessionId changes we must immediately clear
previous users/selection and prevent stale responses from overwriting new state:
inside the useEffect that defines fetchUsers (the effect watching
selectedSessionId and closeAddUsersModal) call setUsers([]) and
setSelectedUserIds([]) right away when selectedSessionId is truthy, then make
the fetch cancellable and/or guarded — create an AbortController and pass its
signal into getUserList (or, if getUserList cannot accept a signal, generate a
local requestId/sessionToken captured by the async response and only call
setUsers when the requestId still matches the latest selectedSessionId); also
abort the controller in the effect cleanup to prevent late responses from
updating state; keep handleKeyDown and its add/remove as-is.

In `@frontend/src/components/attendancemanage/RoundDayPicker.jsx`:
- Around line 146-150: The try/catch in RoundDayPicker.jsx cannot detect
failures because AttendanceContext.jsx's handleAddRounds swallows errors and
resolves; either change handleAddRounds to propagate errors (rethrow the caught
error) or make it return a success flag (e.g., boolean) so callers can branch;
then update RoundDayPicker.jsx to await handleAddRounds and only call
closeAddRoundsModal() when the call returns success or does not throw, and
handle/display the error in the catch branch. Reference the handleAddRounds
function in AttendanceContext.jsx and the closeAddRoundsModal call in
RoundDayPicker.jsx when making the changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5f1a1cb3-40af-4c14-9f6d-6d9e76a5f266

📥 Commits

Reviewing files that changed from the base of the PR and between fe502c2 and 0c84ad1.

📒 Files selected for processing (7)
  • frontend/src/components/Board/CategoryTabs.module.css
  • frontend/src/components/attendancemanage/AddUsersModal.jsx
  • frontend/src/components/attendancemanage/AttendanceManagementCard.jsx
  • frontend/src/components/attendancemanage/AttendanceManagementCard.module.css
  • frontend/src/components/attendancemanage/ConfirmationToast.module.css
  • frontend/src/components/attendancemanage/RoundDayPicker.jsx
  • frontend/src/contexts/AttendanceContext.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/attendancemanage/ConfirmationToast.module.css

Comment on lines +57 to +75
const results = await Promise.allSettled(
idsArray.map((userId) => handleAddUsers(selectedSessionId, userId))
);

const failedCount = results.filter(
(result) => result.status === 'rejected'
).length;

if (failedCount === 0) {
closeAddUsersModal();
return;
}

const successCount = idsArray.length - failedCount;
console.error(
`유저 추가 부분 실패: 성공 ${successCount}명, 실패 ${failedCount}명`,
results.filter((result) => result.status === 'rejected')
);
alert(`유저 추가 중 ${failedCount}명이 실패했습니다.`);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

부분 성공 뒤에 성공한 사용자까지 다시 요청하게 됩니다.

Line 61-75는 실패 건수만 알리고 usersselectedUserIds를 그대로 둡니다. 일부만 추가된 경우 재시도하면 이미 성공한 사용자까지 다시 handleAddUsers로 보내게 되고, 사용자는 어떤 항목이 실패했는지도 알기 어렵습니다. 성공한 항목은 목록에서 제거하고, 선택 상태는 실패한 ID만 남기도록 정리해 두는 편이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/AddUsersModal.jsx` around lines 57 -
75, When partial adds occur (results from Promise.allSettled), you must remove
successfully-added users from the UI state and keep only the failed IDs selected
so retries don't resend already-added users; after computing results from
idsArray and handleAddUsers, derive failedIds =
results.map((r,i)=>({r,i})).filter(r=>r.r.status==='rejected').map(r=>idsArray[r.i])
then update the relevant state: remove successful IDs from users (e.g. update
users/state used in the modal) and set selectedUserIds to failedIds, and log the
failedIds/details; keep the early-close path (failedCount === 0 ->
closeAddUsersModal()) but when some failed, do not close modal — instead update
users and selectedUserIds to only show/keep failures so retries only reattempt
failed IDs and the UI identifies which items failed.

Comment on lines +132 to +162
useEffect(() => {
const requestId = ++fetchRequestIdRef.current;
const isStale = () => requestId !== fetchRequestIdRef.current;

setAttendanceData(EMPTY_ATTENDANCE_DATA);
setSelectedUserIds(new Set());

const fetchAttendanceSheet = async () => {
if (!selectedSessionId) {
return;
}

try {
const data = await getUsers(selectedSessionId);
if (isStale()) return;
setAttendanceData(data || EMPTY_ATTENDANCE_DATA);
} catch (error) {
if (isStale()) return;
console.error('출석부 조회 실패:', error);
setAttendanceData(EMPTY_ATTENDANCE_DATA);
}
};

fetchAttendanceSheet();

return () => {
if (fetchRequestIdRef.current === requestId) {
fetchRequestIdRef.current += 1;
}
};
}, [selectedSessionId, roundAttendanceVersion, roundsVersion]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

세션 전환 후에도 열린 확인 토스트가 남아 있으면 이전 세션에 작업이 실행될 수 있습니다.

confirmAction/confirmRoleChangeActiononConfirm는 토스트 생성 시점의 selectedSessionIdselectedUserIds를 캡처합니다. 그런데 Line 132-162에서는 세션 전환 시 선택 상태만 초기화하고 기존 activeToastId를 닫지 않아서, 화면이 다른 세션으로 바뀐 뒤 확인을 누르면 이전 세션 사용자에게 삭제/권한 변경 요청이 나갈 수 있습니다.

Also applies to: 175-199, 217-243

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/AttendanceManagementCard.jsx` around
lines 132 - 162, When the session changes you must clear any open confirmation
toasts so their onConfirm closures don't operate on stale
selectedSessionId/selectedUserIds; in the useEffect that resets selection (the
block that calls setAttendanceData and setSelectedUserIds) call the toast
dismissal/cleanup used by confirmAction/confirmRoleChangeAction (close
activeToastId or call the toast dismiss helper) before resetting selection, and
ensure confirmAction/confirmRoleChangeAction read current
selectedSessionId/selectedUserIds (or are re-created) so they don't capture old
values; update code around fetchRequestIdRef/useEffect to explicitly clear
activeToastId when selectedSessionId changes.

Comment on lines +182 to +186
onConfirm={async () => {
await onConfirm(selectedSessionId, Array.from(selectedUserIds));
setSelectedUserIds(new Set()); // 성공 후 선택 초기화
closeToast?.();
}}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

부분 실패에서도 선택을 전부 초기화하면 재시도가 어려워집니다.

여기서는 await onConfirm(...) 뒤에 항상 selectedUserIds를 비우고 있습니다. 하지만 handleDeleteUsers/handleAddManager/handleRemoveManager는 부분 실패 시에도 reject하지 않고 알림만 띄우므로, 실패한 대상까지 다시 직접 찾아야 합니다. 결과 요약을 받아 실패한 ID만 유지하거나, 전체 성공일 때만 선택을 초기화해 주세요.

Also applies to: 220-224

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/AttendanceManagementCard.jsx` around
lines 182 - 186, The current onConfirm handler always clears selectedUserIds
after awaiting onConfirm, which loses failed targets when underlying actions
(handleDeleteUsers / handleAddManager / handleRemoveManager) perform partial
failures without rejecting; change the flow so you only clear successful IDs:
update onConfirm (or its callers) to return a result summary (e.g., { failedIds:
string[] } or a success boolean), then in the onConfirm caller check that
result—if result.failedIds is empty (or success === true) clear the selection
and closeToast, otherwise retain failed IDs in selectedUserIds (e.g.,
setSelectedUserIds(new Set(result.failedIds))) so the user can retry;
alternatively, if changing onConfirm is not possible, wrap the call in a
try/catch and only clear selection on explicit success responses from the
functions handleDeleteUsers / handleAddManager / handleRemoveManager.

Comment on lines +584 to +588
.tableGroup {
margin-top: 4px;
max-height: none;
overflow: visible;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

tableGroup의 가로 스크롤을 없애면 회차 열이 작은 화면에서 접근 불가해집니다.

Line 584-588의 overflow: visible이 앞쪽 스크롤 설정을 덮어쓰고 있습니다. 그런데 frontend/src/components/attendancemanage/AttendanceManagementCard.jsx Line 377-390은 회차 수만큼 열을 동적으로 추가하고, 이 파일 Line 591도 최소 폭을 920px로 고정해서 좁은 화면에서는 오른쪽 열을 볼 방법이 사라집니다. 드롭다운 때문에 바깥 표시가 필요하더라도, 테이블을 감싸는 레벨에서는 가로 스크롤이 유지되어야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/AttendanceManagementCard.module.css`
around lines 584 - 588, The .tableGroup CSS rule is overriding the outer scroll
and preventing access to dynamically added "회차" columns on narrow screens;
change .tableGroup to allow horizontal scrolling (e.g., restore overflow-x:auto
or overflow:auto instead of overflow:visible) so the table wrapper can scroll
while keeping any dropdowns’ overflow visible at the inner element level, and
ensure the AttendanceManagementCard component (the table wrapper where min-width
is fixed to 920px) remains scrollable so columns added in
AttendanceManagementCard.jsx (the dynamic column generation around lines
~377-390) remain reachable on small screens.

Comment on lines +98 to +102
.deleteTabButton:disabled {
cursor: not-allowed;
opacity: 0.75;
pointer-events: auto;
}
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

비활성 스타일이 기본 숨김 동작을 깨고 있습니다.

Line 98-102에서 opacity: 0.75를 전역으로 주면, deletingTabId로 전체 버튼이 disabled 될 때 데스크톱에서도 hover/focus 없이 삭제 버튼이 전부 보이게 됩니다. disabled 시각 스타일은 “노출 상태에서만” 적용되도록 분리하는 편이 안전합니다.

수정 제안
 .deleteTabButton:disabled {
   cursor: not-allowed;
-  opacity: 0.75;
   pointer-events: auto;
 }
+
+.tabItem:hover .deleteTabButton:disabled,
+.tabItem:focus-within .deleteTabButton:disabled,
+.tabItem:active .deleteTabButton:disabled,
+.tabItem:focus .deleteTabButton:disabled {
+  opacity: 0.75;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/Board/CategoryTabs.module.css` around lines 98 - 102,
The disabled styles on .deleteTabButton currently apply globally and make all
delete buttons visible when deletingTabId disables them; change the rule so the
visual disabled treatment (opacity: 0.75 and pointer-events: auto) only applies
when the button is actually exposed/visible (e.g., scoped to a visibility
modifier such as .deleteTabButton.visible:disabled or
.deleteTabButton[data-visible="true"]:disabled) and keep the default disabled
behavior otherwise (restore pointer-events to none for truly hidden/disabled
buttons). Update the CSS selector that targets .deleteTabButton:disabled to be
conditional on the visible state and ensure any JS that toggles deletingTabId
also sets that visibility class/attribute on the specific button(s).

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