Skip to content

[fix] 스케줄 컴포넌트 UI 수정#26

Merged
limtjdghks merged 11 commits into
devfrom
fix/ALT-211
May 7, 2026
Merged

[fix] 스케줄 컴포넌트 UI 수정#26
limtjdghks merged 11 commits into
devfrom
fix/ALT-211

Conversation

@limtjdghks
Copy link
Copy Markdown
Member

@limtjdghks limtjdghks commented May 6, 2026

ID

  • ALT-211

변경 내용

  • 스케줄 캘린더 헤더 클릭 시 연/월을 선택할 수 있는 드럼 피커 모달 추가
  • 예상 인건비 데이터를 API에서 받아 캘린더에 표시
  • 캘린더 시작 요일을 일요일로 변경 및 요일별 색상 개선

구현 사항

  • MonthYearPickerModal: 드럼 피커(DrumPicker) 방식의 연/월 선택 모달 구현. 드래그/터치로 스크롤하며 연도(현재 기준 ±5년)와 월을 선택 후 확인 버튼으로 캘린더 날짜 변경
  • useMonthYearPickerViewModel: 피커 모달의 상태(열림/닫힘, 선택된 연/월, 연도 목록) 및 이벤트 핸들러를 관리하는 뷰모델 훅 추가
  • MonthlyCalendar: 헤더의 월 라벨 버튼에 onClick 연결, onMonthChange prop 추가하여 날짜 선택 시 상위 컴포넌트에 변경 전달
  • 예상 인건비: useMonthlySchedulesViewModel에서 estimatedLaborCost 필드를 API 응답으로부터 파싱해 useMonthlyCalendarViewModel에서 텍스트로 포맷(약 N원)하여 캘린더에 노출
  • 캘린더 요일: 시작 요일을 월요일 → 일요일로 변경, 일요일은 빨강, 토요일은 파랑으로 색상 분리

구현 시연 (필요 시)

2026-05-06.11.13.37.mov

참고 사항 (필요 시)

  • 캘린더에서 선택 가능한 연도를 임의로 +-5년으로 정해두었습니다
  • 토요일 색상을 임의로 subBlue로 컬러 토큰을 추가하였습니다

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 월/연도 선택 기능이 추가되어 달력에서 더 쉽게 날짜를 이동할 수 있습니다.
    • 스케줄 보기에 예상 근무 비용 정보가 표시됩니다.
    • 토요일과 일요일이 구분되어 더 명확하게 표시됩니다.
  • 스타일

    • UI 개선을 위한 새로운 색상이 추가되었습니다.

@limtjdghks limtjdghks requested review from dohy-eon and kim3360 May 6, 2026 14:15
@limtjdghks limtjdghks self-assigned this May 6, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
alter-client Ready Ready Preview, Comment May 6, 2026 2:50pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

📋 요약

Walkthrough

달력 인터페이스에 월/연도 선택기 기능을 추가하고, 주말 처리 로직을 토요일/일요일로 분리하며, 예상 노무비 추정 및 표시 기능을 도입했습니다.

Changes

월/연도 선택기 통합

Layer / File(s) Summary
상태 관리 로직
src/features/home/common/schedule/hooks/useMonthYearPickerViewModel.ts
선택기의 열림/닫힘 상태, 연도/월 선택을 관리하는 커스텀 훅 추가. 연도 항목 배열 생성, 인덱스 기반 선택자, 확인 콜백 구현.
UI 컴포넌트
src/features/home/common/schedule/ui/MonthYearPickerModal.tsx
드럼 피커 패턴을 사용한 모달 UI 구현. 포인터 이벤트 처리, 스냅 애니메이션, 두 개의 DrumPicker 인스턴스(연도/월) 렌더링.
캘린더 통합
src/features/home/common/schedule/ui/MonthlyCalendar.tsx, src/features/home/user/schedule/ui/HomeScheduleCalendar.tsx
useMonthYearPickerViewModel 훅과 MonthYearPickerModal 컴포넌트 연결, onMonthChange 핸들러 추가.
페이지 계층 통합
src/pages/manager/home/index.tsx, src/features/home/manager/hooks/useManagerHomeViewModel.ts
일정 객체의 onDateChange 핸들러를 MonthlyCalendar의 onMonthChange로 전달.

캘린더 데이터 형태 및 표시 개선

Layer / File(s) Summary
타입 정의 변경
src/features/home/common/schedule/types/monthlyCalendar.ts, src/features/home/common/schedule/types/calendarView.ts
MonthlyDateCellStateisWeekend boolean을 isSaturday, isSunday로 분리. MonthlyCalendarViewModelweekdayLabels, monthlyDateCellsState, estimatedEarningsText 추가. CalendarSummaryestimatedLaborCost 옵션 추가.
상태 계산 훅
src/features/home/common/schedule/hooks/useMonthlyDateCellsState.ts, src/features/home/common/schedule/hooks/useMonthlyCalendarViewModel.ts
weekDay 값으로부터 isSaturday, isSunday 파생 계산. 예상 노무비 데이터로부터 estimatedEarningsText 포맷 계산(useMemo). WEEKDAY_LABELS_MONDAY_FIRST → WEEKDAY_LABELS 변경.
컴포넌트 업데이트
src/features/home/common/schedule/ui/MonthlyDateCell.tsx
isWeekend prop 제거, isSaturday, isSunday prop 추가. 요일별 색상 로직을 토요일/일요일 개별 처리로 수정.
캘린더 UI & 스타일링
src/features/home/common/schedule/ui/MonthlyCalendar.tsx, tailwind.config.js
평일 레이블에 조건부 클래스 적용(cn 유틸), 첫 번째 요일은 text-error, 마지막 요일은 text-subBlue. Tailwind 색상 토큰 subBlue 추가.
백엔드 통합
src/features/home/manager/hooks/useMonthlySchedulesViewModel.ts
API 응답에서 estimatedLaborCost 파싱하여 캘린더 요약 데이터에 포함.

🎯 시니어 리뷰 포커스

⚠️ 주요 이슈

1. 드럼 피커 성능 및 안정성

  • MonthYearPickerModal.tsx의 DrumPicker는 translate3d 기반 스냅 애니메이션을 사용하지만, 포인터 이벤트 중 동시 여러 손가락 입력(터치) 처리가 없습니다.
    • 위험: 멀티터치 환경에서 예기치 않은 동작 가능성
    • 수정안: pointerup 핸들러에서 e.isPrimary 체크 추가, 또는 초기에 터치 이벤트만 필터링

2. 인덱스 범위 검증 부재

  • useMonthYearPickerViewModel에서 onYearIndexChange, onMonthIndexChange 핸들러가 범위를 벗어난 인덱스를 검증하지 않습니다.
    • 위험: 외부에서 잘못된 인덱스를 전달 시 배열 접근 오류 또는 잘못된 상태
    • 수정안: 핸들러 내에서 Math.max(0, Math.min(index, items.length - 1)) 클램핑 추가

3. 타입 마이그레이션 일관성

  • MonthlyCalendarPropsBase에서 weekdayLabels, monthlyDateCellsState를 제거하고 MonthlyCalendarViewModel으로 옮겼으나, 해당 데이터를 필요로 하는 다른 컴포넌트나 상위 계층에서의 의존성이 명확하지 않습니다.
    • 확인 필요: 제거된 props를 사용하던 다른 소비자가 있는지 확인하고, 마이그레이션 완료 여부 검증

4. 노무비 포맷팅 로직

  • useMonthlyCalendarViewModel에서 estimatedEarningsTextuseMemo로 계산하지만, 포맷 문자열이 하드코딩되어 있고 로케일 대응 여부가 불명확합니다.
    • 개선안: 포맷 함수를 분리하고, i18n 라이브러리 또는 명시적 로케일 파라미터 추가 권장

📝 아키텍처 주의사항

  • 선택기와 캘린더 분리: 월/연도 선택기 로직이 독립적인 훅(useMonthYearPickerViewModel)으로 분리된 것은 좋습니다. 다만, onMonthChange 콜백의 Date 객체 생성 방식(첫 날 기준)이 다른 부분과 일관성이 있는지 확인하세요.

🎯 2 (Simple) | ⏱️ ~25 분


관련 PR

  • alter-app/alter-client#13: 본 PR이 기반하는 공유 일정/캘린더 모듈(MonthlyCalendar, MonthlyDateCell, useMonthlyDateCellsState)에 대한 후속 수정
  • alter-app/alter-client#9: 월간 캘린더 코드 경로(isWeekend vs isSaturday/isSunday, useMonthlyDateCellsState/useMonthlyCalendarViewModel) 동일 함수/타입 수정
  • alter-app/alter-client#20: 동일한 useManagerHomeViewModel 훅 수정(일정 핸들러 스레딩)

제안 리뷰어

  • kim3360
  • dohy-eon
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive PR 제목이 변경 사항의 핵심인 드럼 피커 모달, 예상 인건비, 요일 색상 개선을 모두 포괄하지 못하고 '스케줄 컴포넌트 UI 수정'으로만 표현되어 구체성이 부족합니다. 제목을 더욱 구체적으로 작성하세요. 예: '스케줄 캘린더 월/년 선택 피커 및 예상 인건비 표시 기능 추가' 또는 '[feat] 드럼 피커 모달 추가 및 캘린더 UI 개선'
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션을 완벽하게 따르고 있습니다. ID, 변경 내용, 구현 사항이 명확하고 상세하며, 구현 시연(데모 비디오)과 참고 사항까지 포함되어 있습니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ALT-211

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@limtjdghks limtjdghks changed the title Fix/alt 211 [fix] 스케줄 컴포넌트 UI 수정 May 6, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/features/home/common/schedule/hooks/useMonthlyCalendarViewModel.ts (1)

28-41: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

weekStartsOn: 1weekStartsOn: 0으로 변경 필요 — 요일 헤더와 날짜 컬럼 미정렬

WEEKDAY_LABELS는 일요일 기준(['일','월','화',...])이지만 getMonthlyCellsstartOfWeek/endOfWeek에서 weekStartsOn: 1(월요일)을 사용하므로 그리드의 첫 컬럼(실제는 월요일)에 '일' 헤더가 표시되는 레이아웃 버그가 발생합니다. 다음과 같이 수정하세요:

-  const intervalStart = startOfWeek(monthStart, { weekStartsOn: 1 })
-  const intervalEnd = endOfWeek(monthEnd, { weekStartsOn: 1 })
+  const intervalStart = startOfWeek(monthStart, { weekStartsOn: 0 })
+  const intervalEnd = endOfWeek(monthEnd, { weekStartsOn: 0 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/home/common/schedule/hooks/useMonthlyCalendarViewModel.ts`
around lines 28 - 41, The weekday header vs. grid misalignment is caused by
using weekStartsOn: 1 (Monday) when computing intervalStart/intervalEnd in
useMonthlyCalendarViewModel, while WEEKDAY_LABELS are Sunday-first; update the
startOfWeek and endOfWeek calls that set intervalStart and intervalEnd to use
weekStartsOn: 0 so eachDayOfInterval(...).map(...) (which builds
dateKey/dayText/isCurrentMonth/weekDay) aligns with the Sunday-first
WEEKDAY_LABELS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/home/common/schedule/hooks/useMonthlyDateCellsState.ts`:
- Around line 15-16: The isSaturday/isSunday checks in
useMonthlyDateCellsState.ts are reversed because date.getDay() returns
0=Sunday..6=Saturday; change the predicates so isSaturday uses cell.weekDay ===
6 and isSunday uses cell.weekDay === 0 (update the declarations of isSaturday
and isSunday that reference cell.weekDay in the useMonthlyDateCellsState hook).

In `@src/features/home/common/schedule/hooks/useMonthYearPickerViewModel.ts`:
- Around line 17-20: The years array in useMonthYearPickerViewModel is computed
from currentDate (which is MonthlyCalendar's baseDate) so the displayed year
range shifts when the user navigates to other months; change the reference to a
fixed "today" instead of currentDate—either call getYear(new Date()) (or use the
injectable/testable today value if available) when building years and remove
currentDate from that useMemo dependency so the range is always currentYear ±5;
update the code around the years constant in useMonthYearPickerViewModel.ts to
use getYear(today) (or new Date()) and adjust the dependency list accordingly.

In `@src/features/home/common/schedule/ui/MonthYearPickerModal.tsx`:
- Around line 16-99: DrumPicker currently only handles pointer events so
keyboard and assistive-tech users cannot change month/year; update DrumPicker to
provide keyboard-accessible controls by either (A) adding visible/hidden
increment/decrement <button> elements around the drum (or inside the modal)
wired to onChange (use functions handling +1/-1) with proper aria-labels,
focusable attributes and focus styling, or (B) convert the items list into an
ARIA listbox/option pattern: make the container focusable (tabIndex=0), add
role="listbox" and role="option" on items, manage aria-selected on the currently
selected item, and implement onKeyDown on the container to handle
ArrowUp/ArrowDown/PageUp/PageDown/Home/End and Enter to call
onChange(snappedIndex); keep existing pointer handlers
(handlePointerDown/Move/Up) but ensure keyboard actions update dragDelta/reset
state as needed and that buttons/options are reachable by keyboard and have
accessible names. Ensure focus management (focus stays on the listbox or moves
to buttons) and include sufficient aria labels (e.g., aria-label or
aria-labelledby) to describe the picker to screen readers.
- Around line 124-165: The modal (MonthYearPickerModal) lacks focus management
and Escape handling so keyboard focus can escape to the background; fix by
converting this to a proper dialog: when the modal opens move focus to a
sensible internal control (e.g., the first DrumPicker or the confirm button),
implement a focus trap that keeps Tab/Shift+Tab cycling within the modal, and
handle Escape key to call onClose; optionally replace the markup with the shared
Dialog component if one exists to get these behaviors. Ensure role="dialog"
remains, mark background content inert/aria-hidden while open, and wire up focus
management in the MonthYearPickerModal mount/unmount lifecycle (and ensure
onClose is used for Escape and background click).

---

Outside diff comments:
In `@src/features/home/common/schedule/hooks/useMonthlyCalendarViewModel.ts`:
- Around line 28-41: The weekday header vs. grid misalignment is caused by using
weekStartsOn: 1 (Monday) when computing intervalStart/intervalEnd in
useMonthlyCalendarViewModel, while WEEKDAY_LABELS are Sunday-first; update the
startOfWeek and endOfWeek calls that set intervalStart and intervalEnd to use
weekStartsOn: 0 so eachDayOfInterval(...).map(...) (which builds
dateKey/dayText/isCurrentMonth/weekDay) aligns with the Sunday-first
WEEKDAY_LABELS.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 384ee81d-0a81-462f-914a-f1532a4b7894

📥 Commits

Reviewing files that changed from the base of the PR and between 0043656 and 6cb8622.

📒 Files selected for processing (13)
  • src/features/home/common/schedule/hooks/useMonthYearPickerViewModel.ts
  • src/features/home/common/schedule/hooks/useMonthlyCalendarViewModel.ts
  • src/features/home/common/schedule/hooks/useMonthlyDateCellsState.ts
  • src/features/home/common/schedule/types/calendarView.ts
  • src/features/home/common/schedule/types/monthlyCalendar.ts
  • src/features/home/common/schedule/ui/MonthYearPickerModal.tsx
  • src/features/home/common/schedule/ui/MonthlyCalendar.tsx
  • src/features/home/common/schedule/ui/MonthlyDateCell.tsx
  • src/features/home/manager/hooks/useManagerHomeViewModel.ts
  • src/features/home/manager/hooks/useMonthlySchedulesViewModel.ts
  • src/features/home/user/schedule/ui/HomeScheduleCalendar.tsx
  • src/pages/manager/home/index.tsx
  • tailwind.config.js

Comment thread src/features/home/common/schedule/hooks/useMonthlyDateCellsState.ts Outdated
Comment thread src/features/home/common/schedule/hooks/useMonthYearPickerViewModel.ts Outdated
Comment on lines +16 to +99
function DrumPicker({ items, selectedIndex, onChange }: DrumPickerProps) {
const [dragDelta, setDragDelta] = useState(0)
const [isDragging, setIsDragging] = useState(false)
const startY = useRef(0)

const baseTranslate = (CENTER - selectedIndex) * ITEM_HEIGHT
const rawTranslate = baseTranslate + dragDelta
const minTranslate = (CENTER - (items.length - 1)) * ITEM_HEIGHT
const maxTranslate = CENTER * ITEM_HEIGHT
const currentTranslate = Math.max(
minTranslate,
Math.min(maxTranslate, rawTranslate)
)

const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
setIsDragging(true)
startY.current = e.clientY
setDragDelta(0)
e.currentTarget.setPointerCapture(e.pointerId)
}

const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
if (!isDragging) return
setDragDelta(e.clientY - startY.current)
}

const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
if (!isDragging) return
setIsDragging(false)
const totalDelta = e.clientY - startY.current
const rawIndex = selectedIndex - totalDelta / ITEM_HEIGHT
const snapped = Math.round(
Math.max(0, Math.min(items.length - 1, rawIndex))
)
setDragDelta(0)
onChange(snapped)
}

return (
<div
className="relative overflow-hidden select-none touch-none cursor-grab active:cursor-grabbing"
style={{ height: ITEM_HEIGHT * VISIBLE }}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onPointerCancel={handlePointerUp}
>
<div
className="pointer-events-none absolute inset-x-0 z-10 rounded-lg bg-gray-100 opacity-40"
style={{ top: CENTER * ITEM_HEIGHT, height: ITEM_HEIGHT }}
/>
<div
className="will-change-transform"
style={{
transform: `translateY(${currentTranslate}px)`,
transition: isDragging ? 'none' : 'transform 0.2s ease-out',
}}
>
{items.map((item, i) => (
<div
key={item}
className={cn(
'flex items-center justify-center typography-body01-regular',
i === selectedIndex
? 'text-text-100 font-semibold'
: 'text-text-40'
)}
style={{ height: ITEM_HEIGHT }}
>
{item}
</div>
))}
</div>
<div
className="pointer-events-none absolute inset-x-0 top-0 z-20 bg-gradient-to-b from-white to-transparent"
style={{ height: CENTER * ITEM_HEIGHT }}
/>
<div
className="pointer-events-none absolute inset-x-0 bottom-0 z-20 bg-gradient-to-t from-white to-transparent"
style={{ height: CENTER * ITEM_HEIGHT }}
/>
</div>
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

키보드만으로는 연/월을 변경할 수 없습니다.

DrumPicker가 포인터 이벤트만 처리하고 포커스 가능한 조작 요소도 없어서, 키보드 사용자나 보조기기 사용자는 모달을 열어도 값을 바꿀 수 없습니다. 이 플로우에서는 날짜 선택 자체가 막히므로 button 기반 증감 UI나 listbox 패턴으로 바꿔 방향키/Enter 조작을 지원해주세요. As per coding guidelines, **/*.tsx: a11y: 서비스를 막는 수준(의미 있는 버튼/폼/이미지, 키보드 트랩 등)만.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/home/common/schedule/ui/MonthYearPickerModal.tsx` around lines
16 - 99, DrumPicker currently only handles pointer events so keyboard and
assistive-tech users cannot change month/year; update DrumPicker to provide
keyboard-accessible controls by either (A) adding visible/hidden
increment/decrement <button> elements around the drum (or inside the modal)
wired to onChange (use functions handling +1/-1) with proper aria-labels,
focusable attributes and focus styling, or (B) convert the items list into an
ARIA listbox/option pattern: make the container focusable (tabIndex=0), add
role="listbox" and role="option" on items, manage aria-selected on the currently
selected item, and implement onKeyDown on the container to handle
ArrowUp/ArrowDown/PageUp/PageDown/Home/End and Enter to call
onChange(snappedIndex); keep existing pointer handlers
(handlePointerDown/Move/Up) but ensure keyboard actions update dragDelta/reset
state as needed and that buttons/options are reachable by keyboard and have
accessible names. Ensure focus management (focus stays on the listbox or moves
to buttons) and include sufficient aria labels (e.g., aria-label or
aria-labelledby) to describe the picker to screen readers.

Comment on lines +124 to +165
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<button
type="button"
className="absolute inset-0 bg-black/50"
aria-label="닫기"
onClick={onClose}
/>
<div
role="dialog"
aria-modal="true"
aria-label="날짜 선택"
className="relative w-[280px] rounded-2xl bg-white px-6 pb-5 pt-6"
>
<p className="typography-body01-semibold text-text-90 mb-4 text-center">
날짜 선택
</p>
<div className="flex gap-2">
<div className="flex-1">
<DrumPicker
items={yearItems}
selectedIndex={yearIndex}
onChange={onYearIndexChange}
/>
</div>
<div className="flex-1">
<DrumPicker
items={MONTH_ITEMS}
selectedIndex={monthIndex}
onChange={onMonthIndexChange}
/>
</div>
</div>
<button
type="button"
className="mt-4 w-full rounded-xl bg-main py-3 typography-body01-semibold text-white"
onClick={onConfirm}
>
확인
</button>
</div>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

모달 포커스가 배경으로 새어 나갈 수 있습니다.

role="dialog"aria-modal만으로는 포커스가 모달 안에 머물지 않습니다. 지금 구현은 열릴 때 초기 포커스 이동도 없고 Escape 처리도 없어서, 탭 이동이 배경 UI로 빠지며 키보드 탐색이 깨집니다. 공용 Dialog를 쓰거나, 열릴 때 포커스를 이동하고 내부에 가두는 처리가 필요합니다. As per coding guidelines, **/*.tsx: a11y: 서비스를 막는 수준(의미 있는 버튼/폼/이미지, 키보드 트랩 등)만.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/home/common/schedule/ui/MonthYearPickerModal.tsx` around lines
124 - 165, The modal (MonthYearPickerModal) lacks focus management and Escape
handling so keyboard focus can escape to the background; fix by converting this
to a proper dialog: when the modal opens move focus to a sensible internal
control (e.g., the first DrumPicker or the confirm button), implement a focus
trap that keeps Tab/Shift+Tab cycling within the modal, and handle Escape key to
call onClose; optionally replace the markup with the shared Dialog component if
one exists to get these behaviors. Ensure role="dialog" remains, mark background
content inert/aria-hidden while open, and wire up focus management in the
MonthYearPickerModal mount/unmount lifecycle (and ensure onClose is used for
Escape and background click).

@limtjdghks limtjdghks merged commit 69f7f1e into dev May 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants