[FE] 20260310 fe #294 about attendace manage page#308
[FE] 20260310 fe #294 about attendace manage page#308
Conversation
Walkthrough참석 관리 UI의 다중 선택 기능 확대, 세션 수정/삭제 모달 추가, sub-board 삭제 기능 도입, 시간 입력 개선을 포함한 광범위한 변경. API 핸들러 및 컨텍스트 로직 업데이트. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60분 Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ 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 enforce grammar and style rules using `languagetool`.Configure the |
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.
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx
Outdated
Show resolved
Hide resolved
frontend/src/components/attendancemanage/AttendanceManagementCard.jsx
Outdated
Show resolved
Hide resolved
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
frontend/src/components/attendancemanage/AttendanceManagementCard.module.css
Show resolved
Hide resolved
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
| 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}명이 실패했습니다.`); |
There was a problem hiding this comment.
부분 성공 뒤에 성공한 사용자까지 다시 요청하게 됩니다.
Line 61-75는 실패 건수만 알리고 users와 selectedUserIds를 그대로 둡니다. 일부만 추가된 경우 재시도하면 이미 성공한 사용자까지 다시 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.
| 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]); |
There was a problem hiding this comment.
세션 전환 후에도 열린 확인 토스트가 남아 있으면 이전 세션에 작업이 실행될 수 있습니다.
confirmAction/confirmRoleChangeAction의 onConfirm는 토스트 생성 시점의 selectedSessionId와 selectedUserIds를 캡처합니다. 그런데 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.
| onConfirm={async () => { | ||
| await onConfirm(selectedSessionId, Array.from(selectedUserIds)); | ||
| setSelectedUserIds(new Set()); // 성공 후 선택 초기화 | ||
| closeToast?.(); | ||
| }} |
There was a problem hiding this comment.
부분 실패에서도 선택을 전부 초기화하면 재시도가 어려워집니다.
여기서는 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.
| .tableGroup { | ||
| margin-top: 4px; | ||
| max-height: none; | ||
| overflow: visible; | ||
| } |
There was a problem hiding this comment.
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.
| .deleteTabButton:disabled { | ||
| cursor: not-allowed; | ||
| opacity: 0.75; | ||
| pointer-events: auto; | ||
| } |
There was a problem hiding this comment.
비활성 스타일이 기본 숨김 동작을 깨고 있습니다.
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).
출석관리 페이지 api 연결
출석 관리 페이지 디자인 적용
기타 오류 수정
Summary by CodeRabbit
Release Notes