[FE] 20260310 fe #294 about attendace manage page#308
Conversation
Walkthrough참석 관리 대규모 UI 리팩터링(다중 선택·벌크 작업·역할 관리), 세션 수정/라운드 시간 UI 단순화 및 개선, 서브보드 삭제 권한·UI·API 추가, AttendanceContext·attendanceManage 유틸 시그니처 확장과 광범위한 CSS 변경이 포함된 기능 및 스타일 개편입니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant BoardUI as Board Page / CategoryTabs
participant API as Admin Board API
User->>BoardUI: 서브보드의 삭제 버튼 클릭
BoardUI->>BoardUI: 권한 확인(canDeleteSubBoard) 및 confirm() 실행
BoardUI->>API: DELETE /api/admin/board/{boardId}
API-->>BoardUI: 성공/오류 응답
BoardUI->>BoardUI: 탭 목록 갱신, deletingTabId 초기화, 토스트 표시
BoardUI-->>User: 업데이트된 탭 UI 렌더링
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can use OpenGrep to find security vulnerabilities and bugs across 17+ programming languages.OpenGrep is compatible with Semgrep configurations. Add an |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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-shadow를none으로 재정의하여 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갱신 경로를 하나로 정리하는 게 좋습니다.
handleStartTimeChange와useEffect가 동일 계산으로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-columns가1fr에서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
⛔ Files ignored due to path filters (9)
frontend/package-lock.jsonis excluded by!**/package-lock.jsonfrontend/src/assets/add-user-icon.svgis excluded by!**/*.svgfrontend/src/assets/bin-icon.svgis excluded by!**/*.svgfrontend/src/assets/file-icon.svgis excluded by!**/*.svgfrontend/src/assets/menu-icon.svgis excluded by!**/*.svgfrontend/src/assets/pencil-icon_2.svgis excluded by!**/*.svgfrontend/src/assets/profile-icon.svgis excluded by!**/*.svgfrontend/src/assets/slash-profile-icon.svgis excluded by!**/*.svgfrontend/src/assets/x-icon.svgis excluded by!**/*.svg
📒 Files selected for processing (22)
frontend/package.jsonfrontend/src/components/Board/CategoryTabs.jsxfrontend/src/components/Board/CategoryTabs.module.cssfrontend/src/components/VerificationModal.module.cssfrontend/src/components/attendancemanage/AddUsersModal.jsxfrontend/src/components/attendancemanage/AttendanceManagementCard.jsxfrontend/src/components/attendancemanage/AttendanceManagementCard.module.cssfrontend/src/components/attendancemanage/ConfirmationToast.jsxfrontend/src/components/attendancemanage/ConfirmationToast.module.cssfrontend/src/components/attendancemanage/RoundDayPicker.jsxfrontend/src/components/attendancemanage/SessionManagementCard.jsxfrontend/src/components/attendancemanage/SessionManagementCard.module.cssfrontend/src/components/attendancemanage/SessionModifyModal.jsxfrontend/src/components/attendancemanage/SessionSettingCard.jsxfrontend/src/components/attendancemanage/SessionSettingCard.module.cssfrontend/src/contexts/AttendanceContext.jsxfrontend/src/pages/AttendanceManage.jsxfrontend/src/pages/AttendanceManage.module.cssfrontend/src/pages/Board.jsxfrontend/src/utils/attendanceManage.jsfrontend/src/utils/axios.jsfrontend/src/utils/boardApi.js
There was a problem hiding this comment.
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.jsx의handleAddRounds는 실패 시 로그만 남기고 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
📒 Files selected for processing (7)
frontend/src/components/Board/CategoryTabs.module.cssfrontend/src/components/attendancemanage/AddUsersModal.jsxfrontend/src/components/attendancemanage/AttendanceManagementCard.jsxfrontend/src/components/attendancemanage/AttendanceManagementCard.module.cssfrontend/src/components/attendancemanage/ConfirmationToast.module.cssfrontend/src/components/attendancemanage/RoundDayPicker.jsxfrontend/src/contexts/AttendanceContext.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/attendancemanage/ConfirmationToast.module.css
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
frontend/src/contexts/AttendanceContext.jsx (2)
376-381: 에러 메시지가 누락되어 디버깅이 어렵습니다.
useAttendance훅에서 context가 null일 때 빈Error()를 던지고 있어, 개발자가 문제의 원인을 파악하기 어렵습니다.♻️ 에러 메시지 추가 제안
export const useAttendance = () => { const context = useContext(AttendanceContext); if (context === null) { - throw new Error(); + throw new Error('useAttendance must be used within an AttendanceProvider'); } return context; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/contexts/AttendanceContext.jsx` around lines 376 - 381, The hook useAttendance throws a generic empty Error when AttendanceContext is null, making failure causes unclear; update useAttendance to throw a descriptive Error containing the hook and context name (e.g., "useAttendance must be used within an AttendanceProvider" or similar) so logs and stack traces show why it failed; locate the useAttendance function and replace the throw new Error() with a throw new Error("useAttendance must be used within an AttendanceProvider") (or another clear message) to aid debugging.
132-168:activeToastId가 useEffect의 의존성 배열에 누락되었습니다.Line 137-140에서
activeToastId를 읽고 조건부로toast.dismiss를 호출하지만, 의존성 배열(Line 168)에activeToastId가 포함되어 있지 않습니다. 이로 인해 React의 exhaustive-deps 규칙 위반 경고가 발생할 수 있으며, 세션 변경 시 토스트 정리가 일관되게 동작하지 않을 수 있습니다.다만,
activeToastId를 의존성에 추가하면 토스트 ID 변경 시마다 effect가 재실행될 수 있으므로, 토스트 정리 로직을 별도 effect로 분리하거나 ref로 관리하는 것이 더 적절할 수 있습니다.♻️ 토스트 정리 로직을 ref로 분리하는 제안
+ const activeToastIdRef = useRef(null); + + // activeToastId 상태와 ref 동기화 + useEffect(() => { + activeToastIdRef.current = activeToastId; + }, [activeToastId]); + useEffect(() => { const requestId = ++fetchRequestIdRef.current; const isStale = () => requestId !== fetchRequestIdRef.current; // Dismiss any active confirmation toast when session changes - if (activeToastId) { - toast.dismiss(activeToastId); - setActiveToastId(null); + if (activeToastIdRef.current) { + toast.dismiss(activeToastIdRef.current); + setActiveToastId(null); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/contexts/AttendanceContext.jsx` around lines 132 - 168, The effect currently reads activeToastId and calls toast.dismiss but does not include activeToastId in the useEffect dependency array, causing exhaustive-deps warnings and flaky toast cleanup; locate the useEffect that references activeToastId and toast.dismiss and fix by either (A) moving the toast dismissal into a separate useEffect that lists activeToastId (and any session-related state it should react to) in its dependency array so it runs whenever the toast id changes, or (B) keep a mutable ref (e.g., activeToastIdRef) to store the toast id and read that ref inside the existing effect so you don't need to add the id to the deps; update usages of activeToastId accordingly (or create the ref) to ensure toast.dismiss is invoked reliably without violating exhaustive-deps.frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (2)
123-130: 역할 검증 로직이 여러 문자열 패턴에 의존합니다.
isOwnerRole함수가'OWNER',includes('OWNER'),'세션 생성자'등 여러 패턴을 확인합니다. 이는 방어적 코딩이지만, API 응답의 역할 값이 일관되지 않다는 것을 암시합니다. 가능하다면 API 레이어에서 역할 값을 정규화하는 것이 장기적으로 유지보수에 유리합니다.🤖 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 123 - 130, The isOwnerRole helper currently matches multiple ad-hoc string patterns ('OWNER', includes('OWNER'), '세션 생성자'), which masks inconsistent role values from the API; normalize roles earlier in the flow (preferably in the API/adapter layer) so components can rely on a stable enum-like value, then update isOwnerRole to check against the normalized constant(s) (e.g., ROLE_OWNER) and remove fragile substring checks; locate the normalization point where API responses are parsed and map raw role strings to canonical values, and update AttendanceManagementCard.jsx's isOwnerRole to use those canonical role identifiers.
132-168:activeToastId가 useEffect 의존성 배열에 누락되었습니다.Line 137에서
activeToastId를 읽지만 의존성 배열(Line 168)에 포함되어 있지 않습니다.AttendanceContext.jsx와 동일한 패턴의 문제입니다.♻️ ref를 사용한 해결 방안
+ const activeToastIdRef = useRef(null); + + useEffect(() => { + activeToastIdRef.current = activeToastId; + }, [activeToastId]); useEffect(() => { const requestId = ++fetchRequestIdRef.current; const isStale = () => requestId !== fetchRequestIdRef.current; // Dismiss any active confirmation toast when session changes - if (activeToastId) { - toast.dismiss(activeToastId); + if (activeToastIdRef.current) { + toast.dismiss(activeToastIdRef.current); setActiveToastId(null); }🤖 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 - 168, The effect reads activeToastId (used in toast.dismiss and setActiveToastId) but it is not listed in the useEffect dependency array, causing stale-closure behavior; fix by either adding activeToastId to the dependency array of the useEffect alongside selectedSessionId, roundAttendanceVersion and roundsVersion, or convert activeToastId to a ref (e.g., activeToastIdRef) and read/dismiss via that ref inside the effect to avoid unnecessary re-renders—update references to activeToastId in the effect to use the chosen approach (toast.dismiss, setActiveToastId or activeToastIdRef.current).frontend/src/components/attendancemanage/AttendanceManagementCard.module.css (1)
497-515: 동일 셀렉터가 중복 정의되어 있습니다.
.attendanceManagementCardContainer와.attendanceManagementCardContainer:hover가 파일 내에서 두 번 정의되어 있습니다(Lines 1-15 및 497-515). "Attendance User Management Design Alignment" 주석으로 의도적인 오버라이드로 보이지만, 유지보수 시 혼란을 줄 수 있습니다.가능하다면 기존 정의를 수정하거나, 새 디자인 요구사항에 맞게 통합하는 것이 좋습니다.
🤖 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 497 - 515, Duplicate CSS rules for .attendanceManagementCardContainer and .attendanceManagementCardContainer:hover exist; consolidate them by merging the properties from both definitions into a single .attendanceManagementCardContainer block (ensuring border, border-radius, background, padding, box-shadow, overflow, position, z-index) and include the hover state only once by adding the .attendanceManagementCardContainer:hover rule immediately after with the intended box-shadow: none; remove the earlier/original duplicate blocks so there is one clear definition; keep .dropdownOpenContainer as a separate rule with z-index: 220.frontend/src/components/attendancemanage/AddUsersModal.jsx (1)
12-30: 데이터 페칭과 키보드 핸들러가 하나의 useEffect에 혼합되어 있습니다.현재 useEffect가 데이터 페칭과 ESC 키 핸들러를 함께 처리하고 있어, 관심사 분리가 명확하지 않습니다.
closeAddUsersModal이 의존성에 포함되어 있어, 만약 이 함수가 컨텍스트에서 매번 새로 생성된다면 불필요한 데이터 재요청이 발생할 수 있습니다.♻️ 관심사 분리 제안
+ // 데이터 페칭 useEffect(() => { const fetchUsers = async () => { try { const userList = await getUserList(selectedSessionId); setUsers(userList); } catch (err) { console.error('사용자 목록을 불러오는 데 실패했습니다:', err); } }; if (selectedSessionId) { fetchUsers(); } + }, [selectedSessionId]); + // ESC 키 핸들러 + useEffect(() => { const handleKeyDown = (event) => { if (event.key === 'Escape') closeAddUsersModal(); }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); - }, [selectedSessionId, closeAddUsersModal]); + }, [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, The current useEffect mixes data fetching (fetchUsers/getUserList) with a keyboard handler (handleKeyDown) and includes closeAddUsersModal in the dependencies which can cause unnecessary re-fetches; split into two effects: one useEffect for fetching users that only depends on selectedSessionId and calls fetchUsers when selectedSessionId is set, and a separate useEffect for registering the Escape key handler (handleKeyDown) that adds/removes the keydown listener once; ensure closeAddUsersModal is referenced from a stable source (e.g., memoize it with useCallback in the parent or store it in a ref accessed by handleKeyDown) so the keyboard effect doesn’t re-run when the modal-close function identity changes.frontend/src/components/Board/CategoryTabs.jsx (1)
29-37: 삭제 버튼 노출 조건에 핸들러 존재 여부도 포함하는 것을 권장합니다.
canDeleteSubBoard만 true이고onDeleteSubBoard가 전달되지 않으면 클릭해도 동작하지 않는 버튼이 노출됩니다.제안 수정안
- {canDeleteSubBoard && tab.id !== 'all' && ( + {canDeleteSubBoard && typeof onDeleteSubBoard === 'function' && tab.id !== 'all' && (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/Board/CategoryTabs.jsx` around lines 29 - 37, The delete button is rendered whenever canDeleteSubBoard is true even if the handler onDeleteSubBoard is undefined, causing a non-functional button to appear; update the render condition for the delete button in CategoryTabs.jsx to require onDeleteSubBoard as well (e.g., require canDeleteSubBoard && onDeleteSubBoard && tab.id !== 'all'), or alternatively hide/disable the button when onDeleteSubBoard is missing; ensure you reference the existing identifiers (canDeleteSubBoard, onDeleteSubBoard, tab.id, styles.deleteTabButton) so the fix is applied to the correct element and handler.
🤖 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/Board/CategoryTabs.jsx`:
- Around line 22-25: The tab button in CategoryTabs.jsx should explicitly set
type="button" to match other buttons and prevent accidental form submission;
locate the button element that uses className `${styles.tab} ${activeTab ===
tab.id ? styles.active : ''}` and onClick={() => onTabChange(tab.id)} and add
the type="button" attribute there so the tab behaves like the delete button and
does not act as a submit control.
---
Nitpick comments:
In `@frontend/src/components/attendancemanage/AddUsersModal.jsx`:
- Around line 12-30: The current useEffect mixes data fetching
(fetchUsers/getUserList) with a keyboard handler (handleKeyDown) and includes
closeAddUsersModal in the dependencies which can cause unnecessary re-fetches;
split into two effects: one useEffect for fetching users that only depends on
selectedSessionId and calls fetchUsers when selectedSessionId is set, and a
separate useEffect for registering the Escape key handler (handleKeyDown) that
adds/removes the keydown listener once; ensure closeAddUsersModal is referenced
from a stable source (e.g., memoize it with useCallback in the parent or store
it in a ref accessed by handleKeyDown) so the keyboard effect doesn’t re-run
when the modal-close function identity changes.
In `@frontend/src/components/attendancemanage/AttendanceManagementCard.jsx`:
- Around line 123-130: The isOwnerRole helper currently matches multiple ad-hoc
string patterns ('OWNER', includes('OWNER'), '세션 생성자'), which masks inconsistent
role values from the API; normalize roles earlier in the flow (preferably in the
API/adapter layer) so components can rely on a stable enum-like value, then
update isOwnerRole to check against the normalized constant(s) (e.g.,
ROLE_OWNER) and remove fragile substring checks; locate the normalization point
where API responses are parsed and map raw role strings to canonical values, and
update AttendanceManagementCard.jsx's isOwnerRole to use those canonical role
identifiers.
- Around line 132-168: The effect reads activeToastId (used in toast.dismiss and
setActiveToastId) but it is not listed in the useEffect dependency array,
causing stale-closure behavior; fix by either adding activeToastId to the
dependency array of the useEffect alongside selectedSessionId,
roundAttendanceVersion and roundsVersion, or convert activeToastId to a ref
(e.g., activeToastIdRef) and read/dismiss via that ref inside the effect to
avoid unnecessary re-renders—update references to activeToastId in the effect to
use the chosen approach (toast.dismiss, setActiveToastId or
activeToastIdRef.current).
In
`@frontend/src/components/attendancemanage/AttendanceManagementCard.module.css`:
- Around line 497-515: Duplicate CSS rules for
.attendanceManagementCardContainer and .attendanceManagementCardContainer:hover
exist; consolidate them by merging the properties from both definitions into a
single .attendanceManagementCardContainer block (ensuring border, border-radius,
background, padding, box-shadow, overflow, position, z-index) and include the
hover state only once by adding the .attendanceManagementCardContainer:hover
rule immediately after with the intended box-shadow: none; remove the
earlier/original duplicate blocks so there is one clear definition; keep
.dropdownOpenContainer as a separate rule with z-index: 220.
In `@frontend/src/components/Board/CategoryTabs.jsx`:
- Around line 29-37: The delete button is rendered whenever canDeleteSubBoard is
true even if the handler onDeleteSubBoard is undefined, causing a non-functional
button to appear; update the render condition for the delete button in
CategoryTabs.jsx to require onDeleteSubBoard as well (e.g., require
canDeleteSubBoard && onDeleteSubBoard && tab.id !== 'all'), or alternatively
hide/disable the button when onDeleteSubBoard is missing; ensure you reference
the existing identifiers (canDeleteSubBoard, onDeleteSubBoard, tab.id,
styles.deleteTabButton) so the fix is applied to the correct element and
handler.
In `@frontend/src/contexts/AttendanceContext.jsx`:
- Around line 376-381: The hook useAttendance throws a generic empty Error when
AttendanceContext is null, making failure causes unclear; update useAttendance
to throw a descriptive Error containing the hook and context name (e.g.,
"useAttendance must be used within an AttendanceProvider" or similar) so logs
and stack traces show why it failed; locate the useAttendance function and
replace the throw new Error() with a throw new Error("useAttendance must be used
within an AttendanceProvider") (or another clear message) to aid debugging.
- Around line 132-168: The effect currently reads activeToastId and calls
toast.dismiss but does not include activeToastId in the useEffect dependency
array, causing exhaustive-deps warnings and flaky toast cleanup; locate the
useEffect that references activeToastId and toast.dismiss and fix by either (A)
moving the toast dismissal into a separate useEffect that lists activeToastId
(and any session-related state it should react to) in its dependency array so it
runs whenever the toast id changes, or (B) keep a mutable ref (e.g.,
activeToastIdRef) to store the toast id and read that ref inside the existing
effect so you don't need to add the id to the deps; update usages of
activeToastId accordingly (or create the ref) to ensure toast.dismiss is invoked
reliably without violating exhaustive-deps.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d6a28069-3eda-4dae-a1c2-904db2279d9b
📒 Files selected for processing (5)
frontend/src/components/Board/CategoryTabs.jsxfrontend/src/components/attendancemanage/AddUsersModal.jsxfrontend/src/components/attendancemanage/AttendanceManagementCard.jsxfrontend/src/components/attendancemanage/AttendanceManagementCard.module.cssfrontend/src/contexts/AttendanceContext.jsx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/pages/Board.jsx`:
- Around line 414-468: In handleDeleteSubBoard, also clear or reset the write
target state when the deleted boardId matches the current writeBoardId to avoid
post-create attempts pointing at a removed board; after successful delete
(before resetting deletingSubBoardId) check if writeBoardId === boardId and call
the setter (e.g. setWriteBoardId) to a safe value (ALL_TAB_ID or empty string)
so new writes won't target the deleted sub-board, and ensure this check uses the
same boardId param and write state variable names as in the component.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a7a3042b-0c3f-490c-9b41-084756e64ba8
📒 Files selected for processing (2)
frontend/src/pages/Board.jsxfrontend/src/utils/boardApi.js
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/utils/boardApi.js
출석관리 페이지 api 연결
출석 관리 페이지 디자인 적용
기타 오류 수정
Summary by CodeRabbit
New Features
Style / UX