[feat] 스케줄 관리 페이지 추가 UI 구현#29
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughPR Changes워커 스케줄 UI 및 캘린더 통합
주요 리뷰 포인트🔴 아키텍처
🟡 에러 처리 & 검증
🟡 접근성 & UX
🟠 성능
🔵 기타 고려사항
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/features/user/home/schedule/api/schedule.ts (1)
10-14:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift아키텍처 위반: features 간 의존성
@/features/home/common/schedule/lib/date에서 유틸리티를 import하고 있습니다. FSD 아키텍처에서 features 레이어는 다른 features를 import할 수 없으며, entities와 shared 레이어만 의존해야 합니다.
toDateKey,toTimeLabel,getDurationHours같은 범용 유틸리티는src/shared/lib/date.ts또는src/shared/utils/schedule.ts로 이동해야 합니다.As per coding guidelines: features 레이어는 entities, shared 레이어만 import해야 합니다.
🔧 제안: 유틸리티를 shared 레이어로 이동
src/shared/lib/scheduleDate.ts생성:export function toDateKey(isoString: string): string { // 구현 이동 } export function toTimeLabel(isoString: string): string { // 구현 이동 } export function getDurationHours(start: string, end: string): number { // 구현 이동 }
- 이 파일에서 import 경로 변경:
-} from '@/features/home/common/schedule/lib/date' +} from '@/shared/lib/scheduleDate'🤖 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/user/home/schedule/api/schedule.ts` around lines 10 - 14, The code imports feature-level utilities (toDateKey, toTimeLabel, getDurationHours) from another feature which violates the FSD rule; extract these functions into a shared utility module (e.g., a shared/lib schedule/date utils) and move their implementations there, then update the import in schedule.ts to import toDateKey, toTimeLabel, and getDurationHours from that new shared module instead of '@/features/home/common/schedule/lib/date', ensuring no other feature-to-feature imports remain.
🤖 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/user/home/schedule/lib/date.ts`:
- Around line 17-22: This file imports toDateKey, toTimeLabel, and
getDurationHours from another feature (violating architecture); move those
utility functions into the shared layer (e.g., src/shared/lib or equivalent) and
update imports in this file to import from the shared lib (use
'@/shared/lib/<module>' or your project's shared path); ensure you relocate the
implementations for toDateKey, toTimeLabel, getDurationHours, update any other
files that import them to the new shared location, and run tests/TS build to fix
any path/type issues.
In `@src/features/user/home/workspace/api/workspaceSchedule.ts`:
- Around line 6-11: The imports getDurationHours, toDateKey, toTimeLabel and
formatScheduleTimeRange are coming from another feature layer which violates the
architecture; move the shared scheduling utilities into the shared layer (e.g.,
src/shared/lib scheduling/date helpers) and update all feature imports
(including references in workspaceSchedule.ts and schedule/api/schedule.ts) to
import getDurationHours, toDateKey, toTimeLabel and formatScheduleTimeRange from
the new shared module so features only depend on shared/entities.
In `@src/pages/manager/worker-schedule/index.tsx`:
- Around line 38-63: The page component currently contains UI/domain state
(activeTab, showCalendar, selectedDate, isWorkerDropdownOpen, isColorPickerOpen,
selectedColor and their setters) and some behavior alongside
useWorkerScheduleManageViewModel; extract these UI states and transition logic
into a new feature hook or presenter (e.g., useWorkerScheduleUI or extend
useWorkerScheduleManageViewModel) so the page only composes pieces. Move state
declarations and their handlers (activeTab/setActiveTab,
showCalendar/setShowCalendar, selectedDate/setSelectedDate,
isWorkerDropdownOpen/setIsWorkerDropdownOpen,
isColorPickerOpen/setIsColorPickerOpen, selectedColor/setSelectedColor) and any
UI helpers to that hook, expose minimal props/handlers (e.g., onToggleDay,
toggleDay, onSetStartHour/setStartHour, setStartMinute, setEndHour,
setEndMinute, setSelectedWorkerIndex) and replace the in-file state with calls
to the hook so the page becomes composition-only.
- Around line 22-31: The input currently only strips non-digits so values like
"99" are saved; update the onChange handler on the input to parse the numeric
value, clamp it to the valid range based on the unit (if unit === 'hour' or
similar use 0–23, if unit === 'minute' use 0–59), and then call onChange with
either an empty string for blank input or the clamped, optionally zero-padded
string (two digits) to preserve UI formatting; implement this logic inside the
existing onChange (referencing the input's value, onChange prop, and unit
variable) so invalid hour/minute values are prevented at input time.
- Around line 226-291: The 근무 시간 section is hidden solely based on
isColorPickerOpen, causing it to disappear when switching tabs while the color
picker is open; update the render condition to only hide the section when the
color picker is open AND the currently selected tab is the normal/general tab
(e.g., change the {!isColorPickerOpen && (...)} guard to check both
isColorPickerOpen and your tab state like activeTab/selectedTab === 'normal' so
the section remains visible when switching to the fixed tab); modify the JSX
around the TimeSelectBox blocks to use that combined condition and reference
isColorPickerOpen and your tab state variable (e.g., activeTab or selectedTab)
when making the change.
In `@src/shared/ui/common/ScheduleCalendar.tsx`:
- Around line 17-22: The calendar's viewYear/viewMonth state (created via
useState and setViewYear/setViewMonth in ScheduleCalendar.tsx) is only
initialized from selectedDate and not updated when selectedDate changes; add a
useEffect that watches selectedDate and, when it is non-null, calls
setViewYear(selectedDate.getFullYear()) and
setViewMonth(selectedDate.getMonth()) (optionally guarding to only update if
values differ) so the header month/year stays in sync with external changes to
selectedDate.
In `@src/shared/ui/schedule/ColorPickerDropdown.tsx`:
- Line 2: ColorPickerDropdown currently imports the feature type ScheduleColor
which breaks shared-layer independence; remove the import and instead accept the
color list and its type via props (e.g., add a colors: Array<{ value: string;
label?: string }> prop or a generic Color type) or move the ScheduleColor/type
constants into the shared layer and import that shared type; update the
component signature (ColorPickerDropdown) and any usages to pass the colors prop
(or import the new shared type) and remove any direct dependency on
'@/features/manager/worker-schedule/types/scheduleColor'.
- Around line 48-68: The color buttons in ColorPickerDropdown are not accessible
because the inner circle is aria-hidden and the button lacks an accessible name
and state; update the button element (the one rendering per color in
ColorPickerDropdown) to include an explicit aria-label describing the color
(e.g., the color value or a human-friendly name) and add
aria-pressed={selectedColor === color} to reflect selection state; you can keep
the inner div aria-hidden but ensure the button itself carries the accessible
name and pressed state so screen readers announce the color and whether it is
selected.
---
Outside diff comments:
In `@src/features/user/home/schedule/api/schedule.ts`:
- Around line 10-14: The code imports feature-level utilities (toDateKey,
toTimeLabel, getDurationHours) from another feature which violates the FSD rule;
extract these functions into a shared utility module (e.g., a shared/lib
schedule/date utils) and move their implementations there, then update the
import in schedule.ts to import toDateKey, toTimeLabel, and getDurationHours
from that new shared module instead of
'@/features/home/common/schedule/lib/date', ensuring no other feature-to-feature
imports remain.
🪄 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: 6f23a8a2-d139-494b-8aaf-2566eeab4ae4
⛔ Files ignored due to path filters (1)
src/assets/icons/schedule/schedule_calendar.svgis excluded by!**/*.svg
📒 Files selected for processing (16)
src/features/home/common/schedule/constants/calendar.tssrc/features/home/common/schedule/hooks/useMonthlyCalendarViewModel.tssrc/features/manager/home/hooks/useWorkerScheduleManageViewModel.tssrc/features/manager/index.tssrc/features/manager/schedule/constants/workerSchedule.tssrc/features/manager/schedule/types/workerSchedule.tssrc/features/manager/worker-schedule/types/scheduleColor.tssrc/features/user/home/schedule/api/schedule.tssrc/features/user/home/schedule/constants/calendar.tssrc/features/user/home/schedule/lib/date.tssrc/features/user/home/workspace/api/workspaceSchedule.tssrc/pages/manager/worker-schedule/index.tsxsrc/shared/constants/calendar.tssrc/shared/lib/calendarUtils.tssrc/shared/ui/common/ScheduleCalendar.tsxsrc/shared/ui/schedule/ColorPickerDropdown.tsx
💤 Files with no reviewable changes (2)
- src/features/user/home/schedule/constants/calendar.ts
- src/features/home/common/schedule/constants/calendar.ts
| import { WEEKDAY_LABELS } from '@/shared/constants/calendar' | ||
| import { | ||
| toDateKey, | ||
| toTimeLabel, | ||
| getDurationHours, | ||
| } from '@/features/home/common/schedule/lib/date' |
There was a problem hiding this comment.
아키텍처 위반: features 간 의존성 (세 번째 발생)
Line 17의 @/shared/constants/calendar import는 올바르지만, lines 18-22의 @/features/home/common/schedule/lib/date import는 동일한 아키텍처 위반입니다.
여러 파일에서 반복되는 패턴으로 보아, toDateKey, toTimeLabel, getDurationHours를 src/shared/lib로 이동하는 것이 이 PR의 리팩토링 범위에 포함되어야 합니다.
As per coding guidelines: features 레이어는 entities, shared 레이어만 import해야 합니다.
🤖 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/user/home/schedule/lib/date.ts` around lines 17 - 22, This file
imports toDateKey, toTimeLabel, and getDurationHours from another feature
(violating architecture); move those utility functions into the shared layer
(e.g., src/shared/lib or equivalent) and update imports in this file to import
from the shared lib (use '@/shared/lib/<module>' or your project's shared path);
ensure you relocate the implementations for toDateKey, toTimeLabel,
getDurationHours, update any other files that import them to the new shared
location, and run tests/TS build to fix any path/type issues.
| import { | ||
| getDurationHours, | ||
| toDateKey, | ||
| toTimeLabel, | ||
| getDurationHours, | ||
| formatScheduleTimeRange, | ||
| } from '@/features/user/home/schedule/lib/date' | ||
| } from '@/features/home/common/schedule/lib/date' | ||
| import { formatScheduleTimeRange } from '@/features/user/home/schedule/lib/date' |
There was a problem hiding this comment.
아키텍처 위반: features 간 의존성 (동일 이슈)
@/features/home/common/schedule/lib/date에서 import - 앞서 schedule/api/schedule.ts와 동일한 아키텍처 위반입니다.
해당 유틸리티들을 src/shared/lib로 이동하고 모든 features에서 shared 레이어를 참조하도록 수정해주세요.
As per coding guidelines: features 레이어는 entities, shared 레이어만 import해야 합니다.
🤖 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/user/home/workspace/api/workspaceSchedule.ts` around lines 6 -
11, The imports getDurationHours, toDateKey, toTimeLabel and
formatScheduleTimeRange are coming from another feature layer which violates the
architecture; move the shared scheduling utilities into the shared layer (e.g.,
src/shared/lib scheduling/date helpers) and update all feature imports
(including references in workspaceSchedule.ts and schedule/api/schedule.ts) to
import getDurationHours, toDateKey, toTimeLabel and formatScheduleTimeRange from
the new shared module so features only depend on shared/entities.
| const [activeTab, setActiveTab] = useState<ScheduleTab>('고정') | ||
| const [showCalendar, setShowCalendar] = useState(false) | ||
| const [selectedDate, setSelectedDate] = useState<Date | null>(null) | ||
| const [isWorkerDropdownOpen, setIsWorkerDropdownOpen] = useState(false) | ||
| const [isColorPickerOpen, setIsColorPickerOpen] = useState(false) | ||
| const [selectedColor, setSelectedColor] = useState<ScheduleColor>( | ||
| ScheduleColor.Pink | ||
| ) | ||
| const { | ||
| worker, | ||
| workers, | ||
| selectedWorkerIndex, | ||
| setSelectedWorkerIndex, | ||
| workdayOptions, | ||
| selectedDays, | ||
| workTimeRangeLabel, | ||
| startHour, | ||
| startMinute, | ||
| endHour, | ||
| endMinute, | ||
| setStartHour, | ||
| setStartMinute, | ||
| setEndHour, | ||
| setEndMinute, | ||
| toggleDay, | ||
| } = useWorkerScheduleManageViewModel() |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
페이지 컴포넌트에 도메인 상태/로직이 과도하게 집중되어 있습니다.
탭/달력/드롭다운/색상 상태와 전환 로직이 페이지에 직접 들어와 있어 pages의 조합 역할을 넘고 있습니다. 해당 상태/행동을 feature 레이어 훅(또는 프레젠터 컴포넌트)로 이동해 페이지는 composition 중심으로 정리하는 것을 권장합니다.
As per coding guidelines src/pages/**: 페이지 컴포넌트가 비즈니스 로직 없이 조합(Composition)만 하는지 규칙을 기준으로 확인했습니다.
🤖 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/pages/manager/worker-schedule/index.tsx` around lines 38 - 63, The page
component currently contains UI/domain state (activeTab, showCalendar,
selectedDate, isWorkerDropdownOpen, isColorPickerOpen, selectedColor and their
setters) and some behavior alongside useWorkerScheduleManageViewModel; extract
these UI states and transition logic into a new feature hook or presenter (e.g.,
useWorkerScheduleUI or extend useWorkerScheduleManageViewModel) so the page only
composes pieces. Move state declarations and their handlers
(activeTab/setActiveTab, showCalendar/setShowCalendar,
selectedDate/setSelectedDate, isWorkerDropdownOpen/setIsWorkerDropdownOpen,
isColorPickerOpen/setIsColorPickerOpen, selectedColor/setSelectedColor) and any
UI helpers to that hook, expose minimal props/handlers (e.g., onToggleDay,
toggleDay, onSetStartHour/setStartHour, setStartMinute, setEndHour,
setEndMinute, setSelectedWorkerIndex) and replace the in-file state with calls
to the hook so the page becomes composition-only.
| @@ -0,0 +1,75 @@ | |||
| import chevronDownIcon from '@/assets/icons/home/chevron-down.svg' | |||
| import { ScheduleColor } from '@/features/manager/worker-schedule/types/scheduleColor' | |||
There was a problem hiding this comment.
Shared 레이어에서 Feature 타입을 직접 import하고 있습니다.
ColorPickerDropdown가 feature 타입(ScheduleColor)에 결합되어 shared 독립성이 깨졌습니다. 색상 타입/상수를 shared로 이동하거나, colors를 props로 주입받도록 바꿔 의존 방향을 정리해 주세요.
As per coding guidelines src/shared/**: 다른 레이어를 import하지 않는지 (완전한 독립성) 규칙을 기준으로 확인했습니다.
🤖 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/shared/ui/schedule/ColorPickerDropdown.tsx` at line 2,
ColorPickerDropdown currently imports the feature type ScheduleColor which
breaks shared-layer independence; remove the import and instead accept the color
list and its type via props (e.g., add a colors: Array<{ value: string; label?:
string }> prop or a generic Color type) or move the ScheduleColor/type constants
into the shared layer and import that shared type; update the component
signature (ColorPickerDropdown) and any usages to pass the colors prop (or
import the new shared type) and remove any direct dependency on
'@/features/manager/worker-schedule/types/scheduleColor'.
| <button | ||
| key={color} | ||
| type="button" | ||
| onClick={() => { | ||
| onColorChange(color) | ||
| }} | ||
| className="relative flex items-center justify-center" | ||
| > | ||
| <div | ||
| className={`size-[38px] rounded-full transition-all ${ | ||
| selectedColor === color ? '' : 'hover:scale-110' | ||
| }`} | ||
| style={{ | ||
| backgroundColor: color, | ||
| ...(selectedColor === color && { | ||
| boxShadow: `0 0 0 2px white, 0 0 0 4px ${color}`, | ||
| }), | ||
| }} | ||
| aria-hidden="true" | ||
| /> | ||
| </button> |
There was a problem hiding this comment.
색상 버튼에 접근 가능한 이름이 없습니다.
버튼 내부 원형 요소가 aria-hidden이라 스크린리더에서 어떤 색상인지 알 수 없습니다. 각 버튼에 aria-label과 선택 상태(aria-pressed)를 추가해 주세요.
수정 예시
{colors.map(color => (
<button
key={color}
type="button"
+ aria-label={`색상 ${color}`}
+ aria-pressed={selectedColor === color}
onClick={() => {
onColorChange(color)
}}
className="relative flex items-center justify-center"
>As per coding guidelines **/*.tsx: a11y는 서비스를 막는 수준(의미 있는 버튼/폼/이미지) 규칙을 기준으로 확인했습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| key={color} | |
| type="button" | |
| onClick={() => { | |
| onColorChange(color) | |
| }} | |
| className="relative flex items-center justify-center" | |
| > | |
| <div | |
| className={`size-[38px] rounded-full transition-all ${ | |
| selectedColor === color ? '' : 'hover:scale-110' | |
| }`} | |
| style={{ | |
| backgroundColor: color, | |
| ...(selectedColor === color && { | |
| boxShadow: `0 0 0 2px white, 0 0 0 4px ${color}`, | |
| }), | |
| }} | |
| aria-hidden="true" | |
| /> | |
| </button> | |
| <button | |
| key={color} | |
| type="button" | |
| aria-label={`색상 ${color}`} | |
| aria-pressed={selectedColor === color} | |
| onClick={() => { | |
| onColorChange(color) | |
| }} | |
| className="relative flex items-center justify-center" | |
| > | |
| <div | |
| className={`size-[38px] rounded-full transition-all ${ | |
| selectedColor === color ? '' : 'hover:scale-110' | |
| }`} | |
| style={{ | |
| backgroundColor: color, | |
| ...(selectedColor === color && { | |
| boxShadow: `0 0 0 2px white, 0 0 0 4px ${color}`, | |
| }), | |
| }} | |
| aria-hidden="true" | |
| /> | |
| </button> |
🤖 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/shared/ui/schedule/ColorPickerDropdown.tsx` around lines 48 - 68, The
color buttons in ColorPickerDropdown are not accessible because the inner circle
is aria-hidden and the button lacks an accessible name and state; update the
button element (the one rendering per color in ColorPickerDropdown) to include
an explicit aria-label describing the color (e.g., the color value or a
human-friendly name) and add aria-pressed={selectedColor === color} to reflect
selection state; you can keep the inner div aria-hidden but ensure the button
itself carries the accessible name and pressed state so screen readers announce
the color and whether it is selected.
ID
변경 내용
구현 사항
구현 시연 (필요 시)
Summary by CodeRabbit
출시 노트
새로운 기능
개선 사항