Skip to content

[feat] 스케줄 관리 페이지 추가 UI 구현#29

Merged
limtjdghks merged 11 commits into
devfrom
feat/ALT-213
May 10, 2026
Merged

[feat] 스케줄 관리 페이지 추가 UI 구현#29
limtjdghks merged 11 commits into
devfrom
feat/ALT-213

Conversation

@limtjdghks
Copy link
Copy Markdown
Member

@limtjdghks limtjdghks commented May 10, 2026

ID

  • ALT-213

변경 내용

  • 매니저 근무자 스케줄 관리 페이지 기능 고도화 및 공통 캘린더 UI 컴포넌트 구현

구현 사항

  • 공통 UI 및 유틸리티 구현
    • ScheduleCalendar: 재사용 가능한 공통 캘린더 컴포넌트를 구현하여 날짜 선택 UI 표준화
    • calendarUtils: 캘린더 날짜 계산 로직을 별도 유틸리티로 분리하여 코드 중복 제거 및 유지보수성 향상
    • WEEKDAY_LABELS: 요일 상수를 shared 폴더로 이동하여 전역적으로 참조 가능하도록 개선
  • 매니저 스케줄 관리 기능 강화
    • 탭 분기 처리: '고정' 스케줄과 '일반' 스케줄 타입을 구분하는 탭 UI 및 전환 로직 구현
    • 근무자 선택: 매니저가 관리할 근무자를 선택할 수 있는 드롭다운 리스트 및 필터링 기능 추가
    • 시간 입력 개선: TimeSelectBox를 단순 텍스트 표시에서 숫자 입력(Input)이 가능하도록 수정하여 편의성 증대
    • 색상 선택: 스케줄 구분을 위한 ColorPickerDropdown 컴포넌트 추가 및 적용
    • 날짜 선택: 일반 스케줄 설정 시 특정 날짜를 선택할 수 있도록 ScheduleCalendar 연동
  • 리팩토링 및 아키텍처 개선
    • 여러 Feature에 흩어져 있던 날짜 관련 라이브러리 및 상수를 shared 영역으로 재배치하여 의존성 구조 개선

구현 시연 (필요 시)

image
image

Summary by CodeRabbit

출시 노트

  • 새로운 기능

    • 근무 일정 관리 페이지에 탭 기반 인터페이스 추가
    • 근무 시간을 직접 편집할 수 있는 시간 선택 필드 추가
    • 일정 선택을 위한 달력 피커 추가
    • 일정 구분을 위한 색상 선택 도구 추가
  • 개선 사항

    • 근무자 선택 드롭다운 기능 강화
    • 일정 관리 UI 전반적으로 개선

Review Change Stack

@limtjdghks limtjdghks requested review from dohy-eon and kim3360 May 10, 2026 09:28
@limtjdghks limtjdghks self-assigned this May 10, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 10, 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 10, 2026 10:10am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

Warning

Rate limit exceeded

@limtjdghks has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6939ac1d-f246-4b65-b766-cf9713b527ad

📥 Commits

Reviewing files that changed from the base of the PR and between 7ac5de7 and 47a5776.

📒 Files selected for processing (5)
  • src/features/home/common/schedule/types/monthlyCalendar.ts
  • src/features/home/common/schedule/ui/MonthlyCalendar.tsx
  • src/features/user/home/schedule/lib/date.test.ts
  • src/pages/manager/worker-schedule/index.tsx
  • src/shared/ui/common/ScheduleCalendar.tsx
📝 Walkthrough

Walkthrough

PR #29는 워커 스케줄 관리 UI를 탭 기반으로 재구성하고, 캘린더 상수와 유틸을 공유 계층으로 중앙화하며, 시간 입력을 편집 가능하게 변경합니다. WEEKDAY_LABELSshared/constants로 이동하고, getCalendarCells 유틸과 ScheduleCalendar, ColorPickerDropdown 컴포넌트를 신규 추가합니다.

Changes

워커 스케줄 UI 및 캘린더 통합

계층 / 파일 요약
공유 캘린더 상수 중앙화
src/shared/constants/calendar.ts, src/features/home/common/schedule/constants/calendar.ts, src/features/user/home/schedule/constants/calendar.ts
요일 레이블 상수를 기능별 모듈에서 shared/constants/calendar.ts로 이동합니다. 기존 내보내기는 제거됩니다.
캘린더 그리드 유틸 추가
src/shared/lib/calendarUtils.ts
CalendarCell 인터페이스와 getCalendarCells(baseDate, weekStartsOn) 함수를 신규 정의하여 월 범위 계산 및 주간 정렬을 제공합니다.
스케줄 타입 정의
src/features/manager/schedule/types/workerSchedule.ts, src/features/manager/worker-schedule/types/scheduleColor.ts
ScheduleTab 타입('고정' | '일반')과 ScheduleColor 타입(색상 값 합집합)을 정의합니다.
스케줄 상수 정의
src/features/manager/schedule/constants/workerSchedule.ts
SCHEDULE_TABS 배열을 as const로 정의하여 리터럴 타입 보존을 유지합니다.
공유 UI 컴포넌트
src/shared/ui/common/ScheduleCalendar.tsx, src/shared/ui/schedule/ColorPickerDropdown.tsx
캘린더 월 뷰와 색상 선택 드롭다운 컴포넌트를 구현합니다. 각각 선택 변경 시 콜백을 호출합니다.
훅 상태 관리 리팩토링
src/features/manager/home/hooks/useWorkerScheduleManageViewModel.ts, src/features/home/common/schedule/hooks/useMonthlyCalendarViewModel.ts
시간 입력 필드(startHour/Minute/endHour/Minute)와 워커 선택(selectedWorkerIndex) 상태를 추가하고, 월간 캘린더 셀 생성을 공유 getCalendarCells로 단순화합니다.
임포트 경로 통합
src/features/user/home/schedule/lib/date.ts, src/features/user/home/schedule/api/schedule.ts, src/features/user/home/workspace/api/workspaceSchedule.ts
날짜 유틸(toDateKey, toTimeLabel, getDurationHours) 임포트를 공통 스케줄 라이브러리로 통일합니다. 요일 레이블은 공유 상수에서 임포트합니다.
기능 모듈 배럴 파일
src/features/manager/index.ts
매니저 기능의 배럴 파일에 ScheduleTab 타입과 SCHEDULE_TABS 상수를 재내보내기로 추가합니다.
워커 스케줄 페이지 재구성
src/pages/manager/worker-schedule/index.tsx
TimeSelectBox를 편집 가능한 숫자 입력으로 변경하고, Navbar, 탭 선택, 워커 드롭다운, ScheduleCalendar, ColorPickerDropdown을 통합합니다. 색상 선택기 열림 상태에서 시간 선택 영역을 조건부로 숨깁니다.

주요 리뷰 포인트

🔴 아키텍처

  1. useWorkerScheduleManageViewModel 훅의 모의 데이터 문제

    • 훅 내부에 MOCK_WORKERS 배열이 하드코딩되어 있습니다. 실제 API 또는 전역 상태 관리와의 연결이 명확하지 않으므로, 추후 실제 워커 데이터 통합 시 이 부분의 리팩토링이 필요합니다.
    • 수정안: 모의 데이터는 테스트 시에만 사용하고, 실제 워커 목록은 부모 컴포넌트나 API에서 전달받도록 변경합니다.
  2. 시간 필드 기본값 처리의 불일치

    • startHour, startMinute 등이 빈 문자열로 초기화되고, workTimeRangeLabel 계산 시 결측값을 '00'으로 치환합니다. 사용자 입력이 없을 때의 최종 상태(저장 시 '00'으로 저장될지, 기본값으로 저장될지)가 불명확합니다.
    • 수정안: 초기값을 '00'으로 명시하거나, 저장 전 유효성 검사를 통해 빈 값을 방지하는 로직을 추가합니다.

🟡 에러 처리 & 검증

  1. TimeSelectBox 숫자 입력 유효성 검사 부재

    • sanitizeInput으로 비숫자 문자를 제거하지만, 입력 범위(시간: 0–23, 분: 0–59) 검증이 없습니다. 사용자가 "99"를 입력해도 그대로 저장됩니다.
    • 수정안: onChange 핸들러에서 입력값의 숫자 범위를 검증하여 유효하지 않은 값은 거부하거나 경고합니다.
  2. ScheduleCalendar & ColorPickerDropdown의 Props 검증

    • 컴포넌트가 필수 Props(onDateChange, onColorChange)를 받지 못할 경우 런타임 에러가 발생할 가능성이 있습니다. TypeScript 단에서는 보호되지만, 부모 컴포넌트에서 콜백 전달 누락 시 감지가 어렵습니다.
    • 수정안: 개발 중 콜백 미전달을 방어하기 위해 기본 콜백을 제공하거나, 공식 문서에 필수 Props 명시를 추가합니다.

🟡 접근성 & UX

  1. 색상 선택기 드롭다운 포커스 관리 부재

    • ColorPickerDropdown이 열릴 때 포커스 이동이나 ESC 키 처리가 없어 키보드 사용자가 사용하기 어렵습니다.
    • 수정안: 드롭다운 열림 시 첫 색상 옵션으로 포커스를 이동하고, ESC 키로 닫을 수 있도록 구현합니다.
  2. ScheduleCalendar 월 네비게이션 버튼의 접근성

    • 이전/다음 월 버튼에 aria-label이 없어 스크린 리더 사용자가 버튼의 목적을 알 수 없습니다.
    • 수정안: aria-label="이전 달", aria-label="다음 달" 속성을 추가합니다.

🟠 성능

  1. ScheduleCalendar의 주간 구조 계산
    • 매 렌더링마다 getCalendarCells를 호출하고 weeks 구조를 재계산하고 있습니다. baseDate가 변경되지 않으면 불필요한 계산입니다.
    • 수정안: useMemoweeks 계산을 메모이제이션하여, baseDate 변경 시에만 재계산하도록 최적화합니다.

🔵 기타 고려사항

  1. 페이지의 "저장" 버튼 TODO 주석
    • 시간, 날짜, 색상 입력이 가능하지만, "저장" 버튼의 실제 동작이 구현되지 않았습니다 (TODO 주석 존재).
    • 수정안: 저장 로직을 우선 구현하거나, 이 PR에서는 UI만 완성하고 다음 PR에서 저장 기능을 추가하는지 명확히 합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Possibly related PRs

  • alter-app/alter-client#27: 스케줄/캘린더 상수와 유틸을 재조직하고 임포트 경로를 업데이트하는 기반 작업
  • alter-app/alter-client#26: 월간 캘린더 뷰 모델과 셀 생성 로직을 함께 리팩토링하는 관련 변경

Suggested reviewers

  • kim3360
  • dohy-eon
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (4 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.
Title check ✅ Passed PR 제목은 스케줄 관리 페이지 UI 구현이라는 주요 변경사항을 명확히 반영하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션을 충실히 따르고 있습니다. ID(ALT-213), 변경 내용, 상세한 구현 사항, 시연 이미지가 포함되어 있으며, 전체 맥락이 명확합니다.

✏️ 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 feat/ALT-213

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 Feat/alt 213 [feat] 스케줄 관리 페이지 추가 UI 구현 May 10, 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: 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 레이어로 이동
  1. 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 {
  // 구현 이동
}
  1. 이 파일에서 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

📥 Commits

Reviewing files that changed from the base of the PR and between bc3bcbc and 7ac5de7.

⛔ Files ignored due to path filters (1)
  • src/assets/icons/schedule/schedule_calendar.svg is excluded by !**/*.svg
📒 Files selected for processing (16)
  • src/features/home/common/schedule/constants/calendar.ts
  • src/features/home/common/schedule/hooks/useMonthlyCalendarViewModel.ts
  • src/features/manager/home/hooks/useWorkerScheduleManageViewModel.ts
  • src/features/manager/index.ts
  • src/features/manager/schedule/constants/workerSchedule.ts
  • src/features/manager/schedule/types/workerSchedule.ts
  • src/features/manager/worker-schedule/types/scheduleColor.ts
  • src/features/user/home/schedule/api/schedule.ts
  • src/features/user/home/schedule/constants/calendar.ts
  • src/features/user/home/schedule/lib/date.ts
  • src/features/user/home/workspace/api/workspaceSchedule.ts
  • src/pages/manager/worker-schedule/index.tsx
  • src/shared/constants/calendar.ts
  • src/shared/lib/calendarUtils.ts
  • src/shared/ui/common/ScheduleCalendar.tsx
  • src/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

Comment on lines +17 to 22
import { WEEKDAY_LABELS } from '@/shared/constants/calendar'
import {
toDateKey,
toTimeLabel,
getDurationHours,
} from '@/features/home/common/schedule/lib/date'
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 | 🔴 Critical | 🏗️ Heavy lift

아키텍처 위반: features 간 의존성 (세 번째 발생)

Line 17의 @/shared/constants/calendar import는 올바르지만, lines 18-22의 @/features/home/common/schedule/lib/date import는 동일한 아키텍처 위반입니다.

여러 파일에서 반복되는 패턴으로 보아, toDateKey, toTimeLabel, getDurationHourssrc/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.

Comment on lines 6 to +11
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'
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 | 🔴 Critical | 🏗️ Heavy lift

아키텍처 위반: 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.

Comment thread src/pages/manager/worker-schedule/index.tsx
Comment on lines +38 to 63
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()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Comment thread src/pages/manager/worker-schedule/index.tsx
Comment thread src/shared/ui/common/ScheduleCalendar.tsx
@@ -0,0 +1,75 @@
import chevronDownIcon from '@/assets/icons/home/chevron-down.svg'
import { ScheduleColor } from '@/features/manager/worker-schedule/types/scheduleColor'
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 | ⚡ Quick win

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'.

Comment on lines +48 to +68
<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>
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 | ⚡ Quick win

색상 버튼에 접근 가능한 이름이 없습니다.

버튼 내부 원형 요소가 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.

Suggested change
<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.

Copy link
Copy Markdown
Member

@dohy-eon dohy-eon left a comment

Choose a reason for hiding this comment

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

lgtm

@limtjdghks limtjdghks merged commit f13663e into dev May 10, 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