Skip to content

[Feat] WTH-255: 어드민 일정 관리 UI 구현#47

Merged
JIN921 merged 24 commits intodevelopfrom
feat/WTH-255-어드민-일정-세션관리-UI-구현
Apr 14, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/WTH-255-\uc5b4\ub4dc\ubbfc-\uc77c\uc815-\uc138\uc158\uad00\ub9ac-UI-\uad6c\ud604"
Merged

[Feat] WTH-255: 어드민 일정 관리 UI 구현#47
JIN921 merged 24 commits intodevelopfrom
feat/WTH-255-어드민-일정-세션관리-UI-구현

Conversation

@JIN921
Copy link
Copy Markdown
Collaborator

@JIN921 JIN921 commented Apr 12, 2026

✅ PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📌 관련 이슈번호

  • Closed #255

✅ Key Changes

어드민 일정 관리

  • 일정 목록 페이지: 기수 드롭다운(API 연결), 전체/세션 탭 필터, 월 네비게이터, 검색, 일정 리스트 테이블 구현
  • 일정 생성 모달: 일반 일정/세션 탭, 제목·시작/종료 일시·장소·설명 폼
  • 일정 수정 모달: 기존 데이터 프리필, 변경사항 감지 중도 나가기 플로우, ··· 메뉴에서 삭제 가능
  • 커스텀 달력/시간 피커: Radix Popover 기반 CalendarPicker (월 네비게이션, 오늘 강조, 선택 하이라이트) + TimePicker (오전/오후 시간, 5분 단위 스크롤 리스트)
  • CustomAlertDialog: 위치 지정 가능한(center, top-right, bottom-right 등) 커스텀 확인 다이얼로그
  • 빈 상태 UI: 일정 없을 때 "일정이 없습니다" + "일반 일정 생성하기" 버튼

기반 코드

  • 일정 관련 타입 정의 (Schedule, CreateScheduleBody)
  • adminSchedule API (목록 조회, 생성, 삭제)
  • useAdminSchedules 쿼리 훅, useCreateSchedule / useDeleteSchedule 뮤테이션 훅
  • 날짜 포맷 유틸리티 (scheduleUtils)
  • 어드민 헤더 일정 관리 메타데이터 추가

📸 스크린샷 or 실행영상

image image image

🎸 기타 사항 or 추가 코멘트

  • CustomAlertDialogsrc/components/alert/에 별도로 생성했으며, position prop으로 뜨는 위치를 지정할 수 있습니다.
  • 커스텀 달력/시간 피커는 외부 라이브러리 없이 Radix Popover + 디자인 토큰으로 구현했습니다.

작업사항이 커질 거 같애서 일정이랑 세션은 나눠서 작업하겠습니다!

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 관리자 일정 관리 페이지 추가 - 일정 조회, 생성, 수정, 삭제 기능
    • 달력 및 시간 선택 UI 컴포넌트 추가
    • 날짜별, 월별, 유형별 일정 필터링 기능
    • 일정 검색 기능 추가
    • 확인 대화상자 컴포넌트 추가
  • UI Enhancement

    • 관리자 네비게이션 메뉴 확장 (일정 관리, 게시판 관리, 동아리 관리)
    • 위험 작업 버튼 텍스트 색상 개선

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

일정 관리를 위한 새로운 관리자 인터페이스 기능을 추가합니다. 여러 새로운 일정 관련 UI 컴포넌트, 날짜/시간 선택기, 생성/편집 모달, 타입 정의 및 유틸리티를 포함합니다.

Changes

Cohort / File(s) Summary
Schedule 관리 페이지 및 컨텐츠
src/app/(private)/admin/schedule/page.tsx, src/components/admin/schedule/SchedulePageContent.tsx
일정 관리 페이지에서 SchedulePageContent 컴포넌트를 렌더링합니다. 필터링(기수, 탭, 월/년, 검색), 정렬, 상태 관리 기능이 포함되어 있습니다.
Schedule 목록 표시 컴포넌트
src/components/admin/schedule/ScheduleItem.tsx, src/components/admin/schedule/ScheduleList.tsx, src/components/admin/schedule/ScheduleTag.tsx
ScheduleItem, ScheduleList, ScheduleTag 컴포넌트로 일정을 렌더링합니다. 태그는 일정 타입, 시간, 위치 정보를 배지로 표시합니다. 수정/삭제 액션을 지원합니다.
일정 관리 UI 컴포넌트
src/components/admin/schedule/MonthNavigator.tsx, src/components/admin/schedule/ScheduleFormField.tsx
MonthNavigator는 이전/다음 월 탐색을 제공합니다. ScheduleFormField는 라벨이 있는 폼 필드 컨테이너입니다.
Schedule 모달
src/components/admin/schedule/modal/CreateScheduleModal.tsx, src/components/admin/schedule/modal/EditScheduleModal.tsx
일정 생성 및 편집 모달로 탭(세션/일반), 폼 입력, 저장/취소/삭제 액션을 제공합니다. EditScheduleModal은 변경사항 추적과 확인 다이얼로그를 포함합니다.
날짜/시간 선택 컴포넌트
src/components/ui/CalendarPicker.tsx, src/components/ui/TimePicker.tsx, src/components/ui/DateTimeInput.tsx
CalendarPicker는 월/일 선택, TimePicker는 시간/분 선택(5분 단위), DateTimeInput은 두 컴포넌트를 통합합니다.
Alert 컴포넌트
src/components/alert/CustomAlertDialog.tsx, src/components/alert/index.ts
위치 설정 기능이 있는 커스텀 알림 다이얼로그 컴포넌트를 추가합니다.
타입 및 상수
src/types/admin/schedule.d.ts, src/constants/admin/schedule.constants.ts
Schedule 타입, ScheduleType('SESSION' | 'GENERAL'), 한글 라벨 매핑을 정의합니다.
날짜 유틸리티
src/utils/admin/scheduleUtils.ts, src/utils/shared/date.ts, src/constants/shared/date.ts
일정 날짜 포맷팅(formatScheduleDateTime, formatYearMonth, getDayLabel 등), 월 일수 계산, DAY_NAMES를 DAY_META로 교체합니다.
관리자 인터페이스 업데이트
src/components/admin/index.ts, src/components/admin/layout/Header.tsx, src/assets/icons/admin/index.ts, src/components/ui/index.ts, src/components/ui/Button.tsx
새 Schedule 컴포넌트 재내보내기, 헤더 메타데이터 추가(/admin/schedule, /admin/board, /admin/club-info), 새 아이콘 내보내기, Button danger 색상 변경, UI 컴포넌트 재내보내기.

Sequence Diagram

sequenceDiagram
    participant User as 사용자
    participant SchedulePage as SchedulePage
    participant SchedulePageContent as SchedulePageContent
    participant FilterUI as FilterUI<br/>(기수, 탭, 검색)
    participant ScheduleList as ScheduleList
    participant Modal as Modal<br/>(Create/Edit)

    User->>SchedulePage: 일정 관리 페이지 방문
    SchedulePage->>SchedulePageContent: 렌더링
    SchedulePageContent->>FilterUI: 기수/탭/월/검색 상태 제공
    User->>FilterUI: 필터/검색 선택
    FilterUI->>SchedulePageContent: 필터 값 변경
    SchedulePageContent->>SchedulePageContent: 일정 필터링 및 정렬<br/>(기수 → 월/년 → 탭 → 검색어)
    SchedulePageContent->>ScheduleList: 정렬된 일정 전달
    ScheduleList->>User: 필터된 일정 목록 표시
    
    User->>ScheduleList: "생성" 버튼 클릭
    ScheduleList->>Modal: CreateScheduleModal 열기
    User->>Modal: 일정 입력 및 저장
    Modal->>SchedulePageContent: onOpenChange(false) 콜백
    
    User->>ScheduleList: "수정" 버튼 클릭
    ScheduleList->>SchedulePageContent: 일정 선택
    SchedulePageContent->>Modal: EditScheduleModal 열기
    User->>Modal: 일정 수정 및 저장
    Modal->>SchedulePageContent: 수정 완료 콜백
    SchedulePageContent->>ScheduleList: 변경사항 반영
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • [Feat] WTH-151: 어드민 레이아웃 구현 #6: 동일한 admin Header 구현(src/components/admin/layout/Header.tsx)을 수정합니다. 이전 PR은 Header 컴포넌트를 추가했고, 본 PR은 /admin/schedule 등 새로운 경로를 위해 PAGE_METADATA를 업데이트합니다.
  • [Feat] WTH-219: 홈 api 연결 #30: 동일한 날짜 관련 모듈(src/constants/shared/date.ts, src/utils/shared/date.ts)을 수정합니다. DAY_NAMES를 DAY_META로 교체하고 HOURS/MINUTES를 추가합니다.

Suggested labels

`🎨 Html&css`, `📬 API`

Suggested reviewers

  • dalzzy
  • woneeeee
  • nabbang6

Poem

🐰 달력이 펼쳐지고, 일정들이 춤을 춘다네,
클릭 한 번에 모달 열리고 날짜 골라 담는다,
타이머는 다섯 분씩 살금살금 움직이며,
세션과 일반이 태그에 옹기종기 모여 있고,
관리자의 손에 모든 일정이 담기는 마법! ✨📅

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 '[Feat] WTH-255: 어드민 일정 관리 UI 구현'으로, 변경사항의 핵심인 어드민 일정 관리 UI 구현을 명확하게 설명합니다.
Description check ✅ Passed PR 설명은 필수 템플릿의 모든 주요 섹션(PR 유형, 관련 이슈, Key Changes, 스크린샷, 기타 사항)을 포함하고 있으며, 구현된 기능들이 상세하게 문서화되어 있습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/WTH-255-어드민-일정-세션관리-UI-구현

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.

@github-actions
Copy link
Copy Markdown

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/admin/schedule/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/layout/Header.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/CalendarPicker.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/CreateScheduleModal.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/DateTimeInput.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 실패
Build: 통과

⚠️ 일부 검증에 실패했습니다. 확인 후 수정해주세요.

@github-actions
Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-239odj5fz-weethsite-4975s-projects.vercel.app

@JIN921 JIN921 changed the title feat: 어드민 일정/세션 관리 UI 구현 [Feat] WTH-255: 어드민 일정 관리 UI 구현 Apr 12, 2026
@JIN921 JIN921 requested review from dalzzy, nabbang6 and woneeeee April 12, 2026 11:28
@JIN921 JIN921 self-assigned this Apr 12, 2026
@JIN921 JIN921 added the ✨ Feature 기능 개발 label Apr 12, 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: 13

🧹 Nitpick comments (8)
src/components/admin/schedule/CalendarPicker.tsx (1)

92-92: Popover 그림자에 임의값 대신 디자인 토큰 클래스를 사용해 주세요.

shadow-[0px_4px_14px_0px_rgba(0,0,0,0.25)]는 토큰 기반 스타일 정책과 맞지 않습니다.

As per coding guidelines "Always use design token classes first (text-, bg-, typo-, p-, gap-*) — no hardcoded values; ask before adding new tokens".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/CalendarPicker.tsx` at line 92, The popover
uses a hardcoded shadow utility "shadow-[0px_4px_14px_0px_rgba(0,0,0,0.25)]"
inside the CalendarPicker component's className; replace that hardcoded value
with the appropriate design token shadow class (e.g., the project's shadow token
like shadow-elevation-XX or shadow-lg) to comply with token-based styling, or if
no suitable token exists, request/add a new design token and use its class name
instead; update the className on the element in CalendarPicker.tsx accordingly.
src/components/alert/CustomAlertDialog.tsx (1)

50-50: 다이얼로그 크기/그림자에 하드코딩 값이 포함되어 있습니다.

w-[339px], shadow-[0px_10px_40px_0px_rgba(0,0,0,0.5)]는 토큰 기반 스타일 정책과 충돌합니다. 토큰 클래스 사용 또는 토큰 정의 후 적용이 필요합니다.

As per coding guidelines "Always use design token classes first (text-, bg-, typo-, p-, gap-*) — no hardcoded values; ask before adding new tokens".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/alert/CustomAlertDialog.tsx` at line 50, The component
CustomAlertDialog contains hardcoded utility classes ('w-[339px]' and
'shadow-[0px_10px_40px_0px_rgba(0,0,0,0.5)]') inside the dialog className
string; replace those with design token classes (or add new tokens) and apply
them instead. Locate the className string in CustomAlertDialog and swap
'w-[339px]' for the appropriate width token class (e.g., a dialog width token
like w-dialog) and replace the custom shadow with a shadow token (e.g.,
shadow-dialog); if tokens don’t exist, add the new token definitions to the
design tokens and use those token class names in the same className string.
Ensure you only change the className in CustomAlertDialog and run style linting
to confirm no hardcoded values remain.
src/components/admin/schedule/ScheduleFormField.tsx (1)

1-18: LGTM: 재사용 가능한 폼 필드 래퍼로 잘 분리되었습니다.

label/children 구조와 cn 기반 클래스 병합이 명확해서 모달 폼 구성에 적합합니다.

As per coding guidelines, "Use className merge utility from @/lib/cn".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/ScheduleFormField.tsx` around lines 1 - 18,
Ensure all class strings are merged via the cn utility: in ScheduleFormField
keep the outer wrapper using cn('flex flex-col', className) and change the inner
label container to use cn as well (e.g., cn('flex h-12 items-center px-400')) so
all className usage consistently goes through the '@/lib/cn' merge utility;
update the JSX in the ScheduleFormField function accordingly.
src/components/admin/schedule/ScheduleList.tsx (1)

33-39: 고정 픽셀값(h-[150px], w-[308px])은 토큰 기반 클래스로 치환해 주세요.

현재 값은 디자인 토큰 우선 정책과 충돌합니다. 기존 spacing/size 토큰 조합으로 맞추거나 필요한 경우 토큰 추가 후 사용해 주세요.

As per coding guidelines, "Always use design token classes first (text-, bg-, typo-, p-, gap-*) — no hardcoded values; ask before adding new tokens".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/ScheduleList.tsx` around lines 33 - 39, The
ScheduleList component uses hardcoded utility classes h-[150px] and w-[308px]
(inside the div and the Button with onCreateClick) which violate the
design-token-first rule; replace those fixed pixel classes with the appropriate
design token classes (e.g., existing height/width tokens like h-*, w-* or
spacing tokens that match the intended size) or request/add new tokens if no
suitable token exists, and update the className values on the div and the Button
to use those token classes instead.
src/components/admin/schedule/TimePicker.tsx (1)

68-68: 임의 그림자값(shadow-[...]) 대신 토큰/프리셋 shadow 클래스를 사용해 주세요.

디자인 토큰 정책을 따르려면 bracket arbitrary value는 피하는 편이 안전합니다.

As per coding guidelines, "Always use design token classes first (text-, bg-, typo-, p-, gap-*) — no hardcoded values; ask before adding new tokens".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/TimePicker.tsx` at line 68, In TimePicker.tsx
replace the arbitrary bracket shadow class
"shadow-[0px_4px_14px_0px_rgba(0,0,0,0.25)]" in the className on the root
container with the project’s shadow design token/preset (e.g.,
shadow-md/shadow-lg or the specific token like shadow-elevation-*) so it uses
the standardized token-based shadow class; update the className string in the
TimePicker component accordingly and remove the bracketed arbitrary value to
comply with design token policy.
src/components/admin/schedule/CreateScheduleModal.tsx (1)

65-65: 임의 픽셀값(max-w-[860px], max-h-[700px], h-[150px])은 토큰 기반 클래스로 교체해 주세요.

디자인 시스템 기준으로는 arbitrary value보다 토큰/스케일 클래스 우선이 적합합니다.

As per coding guidelines, "Always use design token classes first (text-, bg-, typo-, p-, gap-*) — no hardcoded values; ask before adding new tokens".

Also applies to: 92-92, 142-142

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/CreateScheduleModal.tsx` at line 65, Replace
arbitrary pixel tailwind classes in CreateScheduleModal.tsx (specifically the
className containing max-w-[860px] at the element tied to the modal, plus
occurrences of max-h-[700px] and h-[150px] noted elsewhere) with the
corresponding design-token/scale classes from our design system (use existing
max-w-*, max-h-*, h-* token classes like the spacing/size scale); if no suitable
token exists, request addition of a new token instead of adding an arbitrary
value. Locate the affected JSX by the component name CreateScheduleModal and the
className strings (the modal container and the two other elements referenced)
and swap pixel-based arbitrary values for token/scale equivalents or open a
token request before introducing new tokens.
src/components/admin/schedule/SchedulePageContent.tsx (1)

172-185: 검색 영역 고정값(w-[492px], pl-14)은 토큰 기반으로 정리해 주세요.

레이아웃 일관성과 디자인 시스템 준수를 위해 arbitrary value 대신 토큰/스케일 클래스를 우선 사용해 주세요.

As per coding guidelines, "Always use design token classes first (text-, bg-, typo-, p-, gap-*) — no hardcoded values; ask before adding new tokens".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/SchedulePageContent.tsx` around lines 172 -
185, The fixed-width and spacing in the search block use arbitrary classes
(w-[492px], pl-14 and left-400) instead of design tokens; in SchedulePageContent
replace these with the project’s scale/token classes (e.g., use a width token
class like w-<token> or p-/px- token and a left-<token> utility) so the Image
and input use consistent token-based sizing and padding; update the containing
div, the Image wrapper class (absolute left-400) and the input padding (pl-14)
to the appropriate token equivalents while preserving the existing hooks/props
(searchValue, setSearchValue, SearchIcon) and visual alignment.
src/components/admin/schedule/EditScheduleModal.tsx (1)

118-123: 아이콘 버튼은 shadcn/ui 프리미티브로 통일을 권장합니다.

메뉴/닫기 버튼을 raw <button>으로 직접 스타일링하기보다, 프로젝트 공통 접근성 패턴(포커스 상태/키보드 동작)이 보장된 프리미티브로 맞추는 편이 유지보수에 유리합니다.

As per coding guidelines "Use Radix UI and shadcn/ui for accessible component primitives".

Also applies to: 133-140

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/EditScheduleModal.tsx` around lines 118 - 123,
The raw <button> wrappers around the menu/close icons (see the icon element
using Icon with AdminMeatballIcon and the similar button at lines 133-140)
should be replaced with the project's shadcn/ui accessible primitive (e.g., the
Button/IconButton component used across the app) so focus/keyboard behavior and
a11y patterns are consistent; locate the component that renders Icon
src={AdminMeatballIcon} and swap the raw button with the shadcn/ui primitive,
preserving existing classNames/props (size, aria-label) and move any custom
styling into the primitive's props or className.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/admin/schedule/CalendarPicker.tsx`:
- Around line 34-37: The viewYear and viewMonth state (initialized from
parsedDate) are only set on first render and thus fall out of sync when the
external prop value changes; add an effect that watches the value prop (or
parsedDate) and calls setViewYear(parsedDate.getFullYear()) and
setViewMonth(parsedDate.getMonth() + 1) to synchronize the calendar view
whenever value updates so prefill/reset actions update the displayed month/year.

In `@src/components/admin/schedule/CreateScheduleModal.tsx`:
- Line 63: The modal's form only resets in handleClose, so closing via outside
click/ESC (Dialog onOpenChange) skips resetForm; update the Dialog's
onOpenChange handler in CreateScheduleModal.tsx (the Dialog at the top and the
similar usage around lines ~49-52) to detect when open becomes false and call
resetForm (or call handleClose) before delegating to the passed onOpenChange
callback, ensuring the form state is cleared on all close paths; locate
resetForm and handleClose in this component and wire them into the Dialog
onOpenChange handler accordingly.
- Around line 54-60: The form currently only checks title and cardinalNumber;
add a start/end time validation so schedules with end < start are rejected by
both isValid and handleSubmit. Locate the start/end state variables (e.g.,
startTime and endTime or startDate/endDate) and update isValid to include a
check like !(endTime < startTime) (or endTime >= startTime), and add the same
guard in handleSubmit before calling handleClose (return early and surface an
error/validation message). Ensure you reference the same variables in both
handleSubmit and isValid so the UI and submit logic stay consistent.
- Line 35: The initial endTime state in CreateScheduleModal is set to '23:59',
which conflicts with the TimePicker's 5-minute step domain; change the default
in the useState call (endTime / setEndTime) to a valid value such as '23:55' (or
compute and round to the nearest 5-minute increment) so the initial selected
value matches the TimePicker options and avoids selection mismatch.

In `@src/components/admin/schedule/EditScheduleModal.tsx`:
- Around line 50-62: The content state is incorrectly initialized to '' and
hasChanges only checks content !== '', which breaks prefill and change detection
for schedules with existing descriptions; initialize content from the incoming
schedule (e.g., useState(() => schedule.description || '') or add a useEffect
that calls setContent(schedule.description || '') when the schedule prop
opens/changes) and update the hasChanges expression to compare content !==
(schedule.description || '') instead of content !== ''. Ensure you update
references to content/state initialization and the hasChanges boolean in
EditScheduleModal to use schedule.description (or the correct schedule field for
description) so existing descriptions are preserved and change detection works.
- Line 101: The className on EditScheduleModal contains arbitrary pixel token
classes (e.g., "max-w-[860px]", "max-h-[700px]", "h-[150px]") which violate the
design-token rule; update these to use existing design token classes (or agreed
new tokens) instead — locate the className string in EditScheduleModal.tsx (and
the other occurrences noted around the other className usages at the same
component locations) and replace max-w-[860px], max-h-[700px], h-[150px] (and
any other bracketed pixel classes found at the noted areas) with the equivalent
token classes (e.g., max-w-*, max-h-*, h-* tokens) or add a new design token if
no equivalent exists, ensuring you follow the project's token naming conventions
and verify with design before introducing new tokens.

In `@src/components/admin/schedule/ScheduleItem.tsx`:
- Around line 32-35: The UI can break when schedule.startDateTime fails to
parse; update the logic around getDayOfMonth, getDayLabel and
formatScheduleDateTime calls to validate schedule.startDateTime first and
provide safe fallbacks (e.g., default strings like "-" or "Invalid date") for
day, dayLabel and dateTimeText when parsing fails; ensure you centralize the
validation (check Date.isNaN or equivalent) before assigning
day/dayLabel/dateTimeText so components consuming those variables never receive
undefined or malformed values.

In `@src/components/admin/schedule/SchedulePageContent.tsx`:
- Around line 70-80: The selectedCardinalId/activeCardinal is only used for
label display and not applied to the schedule filtering pipeline; update the
pipeline that builds monthFiltered (and the subsequent tab/search filters around
the code handling lines 86-93) to also filter schedules by cardinal when
selected by adding a condition like s.cardinalId === selectedCardinalId (or
include all when selectedCardinalId is undefined), so the final displayed list
is filtered by currentYear/currentMonth plus selectedCardinalId and the existing
tab/search criteria; reference selectedCardinalId, activeCardinal, schedules,
monthFiltered, currentYear, currentMonth and the later filter block to locate
where to add this check.
- Around line 117-119: The delete handler (handleDelete) is currently a no-op so
clicking delete doesn't remove items; implement it to perform an optimistic
local update by removing the passed Schedule from the component state (update
the schedules array via setSchedules or equivalent) and close any confirmation
dialog, then call the real delete API (deleteSchedule) or a mock delete function
and handle errors by reverting the state if the API fails; also apply the same
behavior to the other delete spots referenced (the other delete
handlers/confirmation locations) so the UI reflects deletion immediately even
before the backend completes.

In `@src/components/admin/schedule/TimePicker.tsx`:
- Around line 29-35: The current parsing of value in TimePicker (const [h, m] =
value ? value.split(':').map(Number) : [0, 0]) accepts minute values like 59 and
can desync the UI; update the parsing to normalize minutes to the nearest
5-minute step (or fallback to 0 if invalid) before using them in state and
before calling handleSelect/onChange, and apply the same normalization logic to
the other similar block around the handleSelect usage (lines showing minute list
handling) so displayed minute selection always matches the 5-minute increments
used by handleSelect.

In `@src/hooks/mutations/admin/useAdminScheduleMutations.ts`:
- Line 13: The mutation functions in useAdminScheduleMutations.ts call
adminScheduleApi.createSchedule / adminScheduleApi.deleteSchedule with a
non-guarded clubId (using clubId!), which can be undefined if useClubId() is not
ready; update the mutationFn and the corresponding delete mutation to explicitly
guard clubId (from useClubId) before calling adminScheduleApi: check if clubId
is present and if not reject/throw or return a failed Promise with a clear error
message so the mutation never invokes the API with undefined clubId; locate the
mutationFn and delete mutation references to adminScheduleApi.createSchedule and
adminScheduleApi.deleteSchedule and add the guard there.

In `@src/hooks/queries/admin/useAdminScheduleQueries.ts`:
- Around line 11-15: The current queryFn calls
adminScheduleApi.getSchedules(clubId!, cardinalId!) using non-null assertions
and relies only on enabled, which is unsafe because TanStack Query v5 may call
queryFn even when enabled is false; modify the queryFn in
useAdminScheduleQueries to explicitly validate clubId and cardinalId at the
start (e.g., check for null/undefined and throw a clear error or return a
rejected promise) before calling adminScheduleApi.getSchedules, so you never
pass invalid values to adminScheduleApi.getSchedules and the function fails fast
with a descriptive message.

In `@src/utils/admin/scheduleUtils.ts`:
- Around line 3-23: The three functions getDayLabel, getDayOfMonth and
formatScheduleDateTime currently call new Date(dateString) directly which can
mis-interpret timezone-less ISO strings and produce Invalid Date values; update
them to (1) parse the input robustly by detecting timezone-less ISO strings and
appending 'Z' (or otherwise normalizing to UTC) before creating the Date object,
(2) validate the resulting Date (isNaN(date.getTime())) and handle invalid input
by returning a safe fallback (e.g., empty string or null/placeholder) instead of
calling getDay/getDate/getHours on an invalid date, and (3) ensure consistent
use of UTC or local getters (choose getUTC* if you normalize to UTC) so
getDayLabel, getDayOfMonth and formatScheduleDateTime all produce consistent,
timezone-safe output.

---

Nitpick comments:
In `@src/components/admin/schedule/CalendarPicker.tsx`:
- Line 92: The popover uses a hardcoded shadow utility
"shadow-[0px_4px_14px_0px_rgba(0,0,0,0.25)]" inside the CalendarPicker
component's className; replace that hardcoded value with the appropriate design
token shadow class (e.g., the project's shadow token like shadow-elevation-XX or
shadow-lg) to comply with token-based styling, or if no suitable token exists,
request/add a new design token and use its class name instead; update the
className on the element in CalendarPicker.tsx accordingly.

In `@src/components/admin/schedule/CreateScheduleModal.tsx`:
- Line 65: Replace arbitrary pixel tailwind classes in CreateScheduleModal.tsx
(specifically the className containing max-w-[860px] at the element tied to the
modal, plus occurrences of max-h-[700px] and h-[150px] noted elsewhere) with the
corresponding design-token/scale classes from our design system (use existing
max-w-*, max-h-*, h-* token classes like the spacing/size scale); if no suitable
token exists, request addition of a new token instead of adding an arbitrary
value. Locate the affected JSX by the component name CreateScheduleModal and the
className strings (the modal container and the two other elements referenced)
and swap pixel-based arbitrary values for token/scale equivalents or open a
token request before introducing new tokens.

In `@src/components/admin/schedule/EditScheduleModal.tsx`:
- Around line 118-123: The raw <button> wrappers around the menu/close icons
(see the icon element using Icon with AdminMeatballIcon and the similar button
at lines 133-140) should be replaced with the project's shadcn/ui accessible
primitive (e.g., the Button/IconButton component used across the app) so
focus/keyboard behavior and a11y patterns are consistent; locate the component
that renders Icon src={AdminMeatballIcon} and swap the raw button with the
shadcn/ui primitive, preserving existing classNames/props (size, aria-label) and
move any custom styling into the primitive's props or className.

In `@src/components/admin/schedule/ScheduleFormField.tsx`:
- Around line 1-18: Ensure all class strings are merged via the cn utility: in
ScheduleFormField keep the outer wrapper using cn('flex flex-col', className)
and change the inner label container to use cn as well (e.g., cn('flex h-12
items-center px-400')) so all className usage consistently goes through the
'@/lib/cn' merge utility; update the JSX in the ScheduleFormField function
accordingly.

In `@src/components/admin/schedule/ScheduleList.tsx`:
- Around line 33-39: The ScheduleList component uses hardcoded utility classes
h-[150px] and w-[308px] (inside the div and the Button with onCreateClick) which
violate the design-token-first rule; replace those fixed pixel classes with the
appropriate design token classes (e.g., existing height/width tokens like h-*,
w-* or spacing tokens that match the intended size) or request/add new tokens if
no suitable token exists, and update the className values on the div and the
Button to use those token classes instead.

In `@src/components/admin/schedule/SchedulePageContent.tsx`:
- Around line 172-185: The fixed-width and spacing in the search block use
arbitrary classes (w-[492px], pl-14 and left-400) instead of design tokens; in
SchedulePageContent replace these with the project’s scale/token classes (e.g.,
use a width token class like w-<token> or p-/px- token and a left-<token>
utility) so the Image and input use consistent token-based sizing and padding;
update the containing div, the Image wrapper class (absolute left-400) and the
input padding (pl-14) to the appropriate token equivalents while preserving the
existing hooks/props (searchValue, setSearchValue, SearchIcon) and visual
alignment.

In `@src/components/admin/schedule/TimePicker.tsx`:
- Line 68: In TimePicker.tsx replace the arbitrary bracket shadow class
"shadow-[0px_4px_14px_0px_rgba(0,0,0,0.25)]" in the className on the root
container with the project’s shadow design token/preset (e.g.,
shadow-md/shadow-lg or the specific token like shadow-elevation-*) so it uses
the standardized token-based shadow class; update the className string in the
TimePicker component accordingly and remove the bracketed arbitrary value to
comply with design token policy.

In `@src/components/alert/CustomAlertDialog.tsx`:
- Line 50: The component CustomAlertDialog contains hardcoded utility classes
('w-[339px]' and 'shadow-[0px_10px_40px_0px_rgba(0,0,0,0.5)]') inside the dialog
className string; replace those with design token classes (or add new tokens)
and apply them instead. Locate the className string in CustomAlertDialog and
swap 'w-[339px]' for the appropriate width token class (e.g., a dialog width
token like w-dialog) and replace the custom shadow with a shadow token (e.g.,
shadow-dialog); if tokens don’t exist, add the new token definitions to the
design tokens and use those token class names in the same className string.
Ensure you only change the className in CustomAlertDialog and run style linting
to confirm no hardcoded values 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d8884bdd-937d-4193-ae67-a51f690df609

📥 Commits

Reviewing files that changed from the base of the PR and between b5a0aa7 and a1555ab.

⛔ Files ignored due to path filters (3)
  • src/assets/icons/admin/ic_admin_calendar_edit.svg is excluded by !**/*.svg
  • src/assets/icons/admin/ic_admin_square_left.svg is excluded by !**/*.svg
  • src/assets/icons/admin/ic_admin_square_right.svg is excluded by !**/*.svg
📒 Files selected for processing (26)
  • src/app/(private)/admin/schedule/page.tsx
  • src/assets/icons/admin/index.ts
  • src/components/admin/index.ts
  • src/components/admin/layout/Header.tsx
  • src/components/admin/schedule/CalendarPicker.tsx
  • src/components/admin/schedule/CreateScheduleModal.tsx
  • src/components/admin/schedule/DateTimeInput.tsx
  • src/components/admin/schedule/EditScheduleModal.tsx
  • src/components/admin/schedule/MonthNavigator.tsx
  • src/components/admin/schedule/ScheduleFormField.tsx
  • src/components/admin/schedule/ScheduleItem.tsx
  • src/components/admin/schedule/ScheduleList.tsx
  • src/components/admin/schedule/SchedulePageContent.tsx
  • src/components/admin/schedule/ScheduleTag.tsx
  • src/components/admin/schedule/TimePicker.tsx
  • src/components/alert/CustomAlertDialog.tsx
  • src/components/alert/index.ts
  • src/components/ui/Button.tsx
  • src/hooks/mutations/admin/index.ts
  • src/hooks/mutations/admin/useAdminScheduleMutations.ts
  • src/hooks/queries/admin/index.ts
  • src/hooks/queries/admin/useAdminScheduleQueries.ts
  • src/lib/apis/adminSchedule.ts
  • src/lib/apis/index.ts
  • src/types/admin/schedule.d.ts
  • src/utils/admin/scheduleUtils.ts

Comment thread src/components/ui/CalendarPicker.tsx
Comment thread src/components/admin/schedule/modal/CreateScheduleModal.tsx Outdated
Comment thread src/components/admin/schedule/modal/CreateScheduleModal.tsx Outdated
Comment thread src/components/admin/schedule/modal/CreateScheduleModal.tsx Outdated
Comment thread src/components/admin/schedule/modal/EditScheduleModal.tsx Outdated
Comment thread src/components/admin/schedule/SchedulePageContent.tsx
Comment thread src/components/ui/TimePicker.tsx Outdated
Comment thread src/hooks/mutations/admin/useAdminScheduleMutations.ts Outdated
Comment thread src/hooks/queries/admin/useAdminScheduleQueries.ts Outdated
Comment thread src/utils/admin/scheduleUtils.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/admin/schedule/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/layout/Header.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/CalendarPicker.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/CreateScheduleModal.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/DateTimeInput.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 실패
Build: 통과

⚠️ 일부 검증에 실패했습니다. 확인 후 수정해주세요.

@github-actions
Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-2pzjxmsh9-weethsite-4975s-projects.vercel.app

- adminSchedule API, query/mutation hooks, barrel export 변경 되돌림
- CreateScheduleBody 타입 제거

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CalendarPicker, TimePicker, DateTimeInput → components/ui/ 이동
- CreateScheduleModal, EditScheduleModal → admin/schedule/modal/ 이동
- barrel export 경로 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/admin/schedule/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/layout/Header.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/MonthNavigator.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/ScheduleFormField.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/ScheduleItem.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 실패
Build: 통과

⚠️ 일부 검증에 실패했습니다. 확인 후 수정해주세요.

@github-actions
Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-lopxuipqd-weethsite-4975s-projects.vercel.app

Copy link
Copy Markdown
Collaborator

@nabbang6 nabbang6 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~!! 어제 회의에서 UX 측면에서 한번 체크해보는 게 좋겟다고 하셧어서... 클로드에게 web-design-guidelines 스킬로 체크 부탁해봣는데 ㅎ_ㅎ 그 내용들 바탕으로 코멘트 몇 개 달아두엇습니당

깔끔하게 잘 구현해주신 것 같아서 요대로 쭉 진행해도 될 것 같아용!! 쵝오,,,b

return (
<div className={cn('flex flex-col', className)} {...props}>
<div className="flex h-12 items-center px-400">
<span className="typo-sub2 text-text-normal">{label}</span>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

<span>으로 label 텍스트를 표시해도 큰 문제는 없지만,, <label> 태그로 가져오면 스크린 리더가 읽을 수 잇어 접근성 측면에서 더 좋다고 합니당!

Comment thread src/components/admin/schedule/SchedulePageContent.tsx
Comment thread src/components/admin/schedule/modal/EditScheduleModal.tsx
Copy link
Copy Markdown
Member

@dalzzy dalzzy left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~!!! 👍🏻 열일하는 직장인,,,, 파이팅..

Comment thread src/components/ui/TimePicker.tsx
Comment thread src/components/ui/CalendarPicker.tsx
Comment thread src/components/admin/schedule/ScheduleItem.tsx
Comment thread src/components/alert/CustomAlertDialog.tsx Outdated
Comment thread src/components/ui/CalendarPicker.tsx Outdated
Comment thread src/utils/admin/scheduleUtils.ts Outdated
Copy link
Copy Markdown
Member

@woneeeee woneeeee left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!!! 끝이 보이네용 히히 👀

Comment thread src/components/admin/schedule/modal/CreateScheduleModal.tsx Outdated
Comment thread src/components/admin/schedule/modal/EditScheduleModal.tsx Outdated
Comment thread src/components/admin/schedule/modal/EditScheduleModal.tsx Outdated
Comment thread src/components/admin/schedule/ScheduleItem.tsx Outdated
Comment thread src/components/admin/schedule/SchedulePageContent.tsx
Comment thread src/components/ui/CalendarPicker.tsx Outdated
Comment thread src/components/ui/TimePicker.tsx Outdated
Comment thread src/types/admin/schedule.d.ts
@github-actions
Copy link
Copy Markdown

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/(private)/admin/schedule/page.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/layout/Header.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/MonthNavigator.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/ScheduleFormField.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/schedule/ScheduleItem.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions
Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions
Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-hny4dgade-weethsite-4975s-projects.vercel.app

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: 3

♻️ Duplicate comments (2)
src/components/admin/schedule/SchedulePageContent.tsx (1)

122-124: ⚠️ Potential issue | 🟠 Major

삭제 핸들러가 비어 있어 실제 삭제가 반영되지 않습니다.

ScheduleListEditScheduleModal 둘 다 이 핸들러를 호출하지만, 현재는 어떤 상태 변경도 일어나지 않아 사용자는 삭제가 된 것처럼 보였다가 목록이 그대로 남습니다. mock 단계여도 로컬 목록에서 즉시 제거하거나, 실제 mutation 성공 후에만 닫히도록 연결이 필요합니다.

수정 예시
-  const schedules = MOCK_SCHEDULES;
+  const [schedules, setSchedules] = useState<Schedule[]>(MOCK_SCHEDULES);

-  const handleDelete = (_schedule: Schedule) => {
-    // TODO: API 연동 시 deleteSchedule(_schedule) 호출
-  };
+  const handleDelete = (schedule: Schedule) => {
+    setSchedules((prev) => prev.filter((item) => item.scheduleId !== schedule.scheduleId));
+    // TODO: API 연동 시 deleteSchedule(schedule) 호출
+  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/SchedulePageContent.tsx` around lines 122 -
124, The handleDelete function is currently a no-op so deletions never
propagate; implement it to call the deletion flow (call the existing
deleteSchedule API/mutation if available or a mock delete) and upon success
update the local schedules state used by ScheduleList and EditScheduleModal by
filtering out the deleted Schedule (use its id), ensure the EditScheduleModal is
closed only after successful deletion, and add basic error handling/logging to
revert UI or show an error if the API fails; locate and change the handleDelete
function referenced by ScheduleList and EditScheduleModal and update the
component's schedules state updater accordingly.
src/components/admin/schedule/modal/EditScheduleModal.tsx (1)

33-42: ⚠️ Potential issue | 🟠 Major

설명 필드가 항상 빈 값으로 초기화되어 기존 설명을 잃습니다.

toInitialForm()content''로 고정해서 수정 모달을 열 때마다 설명 프리필이 깨지고, 변경 감지도 설명 필드에서는 항상 왜곡됩니다. 게다가 현재 src/types/admin/schedule.d.ts:1-11Schedule에도 설명 필드가 없어 이 모달이 기존 설명을 복원할 방법이 없습니다.

수정 방향 예시
 function toInitialForm(schedule: Schedule): ScheduleFormState {
   return {
     title: schedule.title,
     startDate: schedule.startDateTime.slice(0, 10),
     startTime: schedule.startDateTime.slice(11, 16),
     endDate: schedule.endDateTime.slice(0, 10),
     endTime: schedule.endDateTime.slice(11, 16),
     location: schedule.location,
-    content: '',
+    content: schedule.description ?? '',
   };
 }
// src/types/admin/schedule.d.ts
export interface Schedule {
  scheduleId: number;
  title: string;
  type: ScheduleType;
  startDateTime: string;
  endDateTime: string;
  location: string;
  cardinalNumber: number;
  description?: string;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/modal/EditScheduleModal.tsx` around lines 33 -
42, The toInitialForm function wipes existing descriptions by setting content to
'' and the Schedule type lacks a description field; update the Schedule
interface to include an optional description?: string and modify toInitialForm
(the function named toInitialForm) to set content = schedule.description ?? ''
so the edit modal pre-fills the existing description while still defaulting to
empty when absent.
🧹 Nitpick comments (4)
src/components/ui/TimePicker.tsx (1)

10-13: UI 컴포넌트에 className prop이 노출되지 않았습니다.

CalendarPicker와 동일하게 className prop을 노출하여 외부에서 스타일을 확장할 수 있도록 해야 합니다.

♻️ className 지원 추가
 interface TimePickerProps {
   value: string;
   onChange: (value: string) => void;
+  className?: string;
 }

그리고 trigger 버튼에 cn()으로 병합:

-function TimePicker({ value, onChange }: TimePickerProps) {
+function TimePicker({ value, onChange, className }: TimePickerProps) {
   // ...
         <button
           type="button"
-          className="bg-container-neutral data-[state=open]:border-brand-primary ..."
+          className={cn("bg-container-neutral data-[state=open]:border-brand-primary ...", className)}

As per coding guidelines: "Always expose className prop in component Props" for src/components/ui/**/*.{ts,tsx}.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/TimePicker.tsx` around lines 10 - 13, TimePickerProps
currently lacks a className prop so external styles can't be applied; add
className?: string to the TimePickerProps interface and accept it in the
TimePicker component props, then pass it into the top-level container (or
trigger button) by merging with existing classes using the cn(...) utility (same
approach as CalendarPicker) so external className values are preserved and
combined.
src/utils/shared/date.ts (1)

64-68: formatDateDisplay가 월/일의 앞자리 0을 제거하지 않습니다.

함수 목적이 "YYYY. M. D." 형식이라면, 입력 "2026-04-05""2026. 4. 5."로 출력되어야 하지만 현재는 "2026. 04. 05."를 반환합니다.

♻️ 앞자리 0 제거가 필요한 경우
 export function formatDateDisplay(dateStr: string): string {
   if (!dateStr) return '';
   const [year, month, day] = dateStr.split('-');
-  return `${year}. ${month}. ${day}.`;
+  return `${year}. ${Number(month)}. ${Number(day)}.`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/shared/date.ts` around lines 64 - 68, The function
formatDateDisplay currently preserves leading zeros in month/day; update it to
remove them by converting month and day to numbers (e.g., using parseInt or
Number) or by stripping a leading '0' before formatting so "2026-04-05" becomes
"2026. 4. 5."; modify the logic inside formatDateDisplay (referenced by its
name) to coerce or trim month and day values prior to building the return string
while keeping the existing YYYY. M. D. output structure.
src/components/ui/CalendarPicker.tsx (1)

12-15: UI 컴포넌트에 className prop이 노출되지 않았습니다.

src/components/ui/ 내의 컴포넌트는 className prop을 노출해야 합니다.

♻️ className 지원 추가
 interface CalendarPickerProps {
   value: string;
   onChange: (value: string) => void;
+  className?: string;
 }

-function CalendarPicker({ value, onChange }: CalendarPickerProps) {
+function CalendarPicker({ value, onChange, className }: CalendarPickerProps) {

그리고 trigger 버튼에 적용:

         <button
           type="button"
-          className="bg-container-neutral data-[state=open]:border-brand-primary ..."
+          className={cn("bg-container-neutral data-[state=open]:border-brand-primary ...", className)}

As per coding guidelines: "Always expose className prop in component Props" for src/components/ui/**/*.{ts,tsx}.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/CalendarPicker.tsx` around lines 12 - 15, The
CalendarPicker component props (CalendarPickerProps) currently omit className;
update the CalendarPickerProps interface to accept an optional className?:
string and thread that prop into the CalendarPicker component so consumers can
pass classes; also apply the className to the root element (or the component's
outer container) and ensure the trigger button inside CalendarPicker (the
trigger element) receives any relevant classes or composes them (e.g., merge
with existing classes) so styling can be applied from outside.
src/components/admin/schedule/SchedulePageContent.tsx (1)

177-177: w-[492px]는 현재 토큰 규칙을 우회합니다.

이 검색 영역 폭은 임의 픽셀값 대신 기존 사이즈 토큰으로 표현하는 편이 맞습니다. 대응 토큰이 없다면 여기서 arbitrary value를 늘리기보다 토큰 추가 합의를 먼저 하는 편이 안전합니다.

As per coding guidelines "Always use design token classes first (text-, bg-, typo-, p-, gap-*) — no hardcoded values; ask before adding new tokens".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/SchedulePageContent.tsx` at line 177, The div
in SchedulePageContent.tsx currently uses an arbitrary width class "w-[492px]"
which violates the token-first rule; replace this hardcoded width by using an
existing design token width class (e.g., a w- token from your size scale) for
the search area or, if no suitable token exists, open a token addition RFC/PR
and use a new token class (e.g., w-{new-token}) once agreed; update the JSX
className on the div (the element with className containing "relative
w-[492px]") to the tokenized width and remove the arbitrary bracketed value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/admin/schedule/modal/CreateScheduleModal.tsx`:
- Line 35: resetForm currently calls setEndTime('23:59'), which conflicts with
the TimePicker's 5-minute step and the component's initial endTime value of
'23:55'; update resetForm to use '23:55' instead (or better, reference a single
DEFAULT_END_TIME constant used by the initial state) so resetForm, the initial
state, and the TimePicker step are consistent (refer to resetForm and setEndTime
in CreateScheduleModal).
- Line 51: isValid currently uses startDate < endDate which can fail to consider
time components (or compare non-Date values); update the validation to compare
timestamps instead by using startDate.getTime() < endDate.getTime() (or, if
startDate/endDate may be strings, first parse them to Date and then compare
.getTime()), keeping the other checks (title.trim() and cardinalNumber !== null)
and referencing the isValid expression and the startDate/endDate/cardinalNumber
variables.

In `@src/components/admin/schedule/modal/EditScheduleModal.tsx`:
- Around line 78-82: handleSubmit currently only validates title and calls
handleClose, so changes never propagate; update it to call the parent save
handler or API mutation (e.g., an onSave prop or updateSchedule mutation) with
the current form state, await the result, and only call handleClose on success;
also surface errors (keep modal open on failure) and toggle a local saving flag
(e.g., isSaving) to disable the save button while the request is in flight;
reference the handleSubmit function, handleClose, the form state (form.title and
other fields), and the parent onSave/updateSchedule mutation to locate the
changes.

---

Duplicate comments:
In `@src/components/admin/schedule/modal/EditScheduleModal.tsx`:
- Around line 33-42: The toInitialForm function wipes existing descriptions by
setting content to '' and the Schedule type lacks a description field; update
the Schedule interface to include an optional description?: string and modify
toInitialForm (the function named toInitialForm) to set content =
schedule.description ?? '' so the edit modal pre-fills the existing description
while still defaulting to empty when absent.

In `@src/components/admin/schedule/SchedulePageContent.tsx`:
- Around line 122-124: The handleDelete function is currently a no-op so
deletions never propagate; implement it to call the deletion flow (call the
existing deleteSchedule API/mutation if available or a mock delete) and upon
success update the local schedules state used by ScheduleList and
EditScheduleModal by filtering out the deleted Schedule (use its id), ensure the
EditScheduleModal is closed only after successful deletion, and add basic error
handling/logging to revert UI or show an error if the API fails; locate and
change the handleDelete function referenced by ScheduleList and
EditScheduleModal and update the component's schedules state updater
accordingly.

---

Nitpick comments:
In `@src/components/admin/schedule/SchedulePageContent.tsx`:
- Line 177: The div in SchedulePageContent.tsx currently uses an arbitrary width
class "w-[492px]" which violates the token-first rule; replace this hardcoded
width by using an existing design token width class (e.g., a w- token from your
size scale) for the search area or, if no suitable token exists, open a token
addition RFC/PR and use a new token class (e.g., w-{new-token}) once agreed;
update the JSX className on the div (the element with className containing
"relative w-[492px]") to the tokenized width and remove the arbitrary bracketed
value.

In `@src/components/ui/CalendarPicker.tsx`:
- Around line 12-15: The CalendarPicker component props (CalendarPickerProps)
currently omit className; update the CalendarPickerProps interface to accept an
optional className?: string and thread that prop into the CalendarPicker
component so consumers can pass classes; also apply the className to the root
element (or the component's outer container) and ensure the trigger button
inside CalendarPicker (the trigger element) receives any relevant classes or
composes them (e.g., merge with existing classes) so styling can be applied from
outside.

In `@src/components/ui/TimePicker.tsx`:
- Around line 10-13: TimePickerProps currently lacks a className prop so
external styles can't be applied; add className?: string to the TimePickerProps
interface and accept it in the TimePicker component props, then pass it into the
top-level container (or trigger button) by merging with existing classes using
the cn(...) utility (same approach as CalendarPicker) so external className
values are preserved and combined.

In `@src/utils/shared/date.ts`:
- Around line 64-68: The function formatDateDisplay currently preserves leading
zeros in month/day; update it to remove them by converting month and day to
numbers (e.g., using parseInt or Number) or by stripping a leading '0' before
formatting so "2026-04-05" becomes "2026. 4. 5."; modify the logic inside
formatDateDisplay (referenced by its name) to coerce or trim month and day
values prior to building the return string while keeping the existing YYYY. M.
D. output structure.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20cc85d8-27b3-4a76-9e24-b18a9a510305

📥 Commits

Reviewing files that changed from the base of the PR and between a1555ab and e91a5f0.

📒 Files selected for processing (19)
  • src/components/admin/index.ts
  • src/components/admin/schedule/ScheduleFormField.tsx
  • src/components/admin/schedule/ScheduleItem.tsx
  • src/components/admin/schedule/ScheduleItem.tsx.__tmp
  • src/components/admin/schedule/ScheduleList.tsx
  • src/components/admin/schedule/SchedulePageContent.tsx
  • src/components/admin/schedule/ScheduleTag.tsx
  • src/components/admin/schedule/modal/CreateScheduleModal.tsx
  • src/components/admin/schedule/modal/EditScheduleModal.tsx
  • src/components/alert/CustomAlertDialog.tsx
  • src/components/ui/CalendarPicker.tsx
  • src/components/ui/DateTimeInput.tsx
  • src/components/ui/TimePicker.tsx
  • src/components/ui/index.ts
  • src/constants/admin/schedule.constants.ts
  • src/constants/shared/date.ts
  • src/types/admin/schedule.d.ts
  • src/utils/admin/scheduleUtils.ts
  • src/utils/shared/date.ts
✅ Files skipped from review due to trivial changes (2)
  • src/components/ui/index.ts
  • src/types/admin/schedule.d.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/components/admin/schedule/ScheduleFormField.tsx
  • src/components/admin/schedule/ScheduleItem.tsx
  • src/components/admin/index.ts
  • src/utils/admin/scheduleUtils.ts
  • src/components/admin/schedule/ScheduleList.tsx
  • src/components/alert/CustomAlertDialog.tsx

setStartDate(toDateInputValue());
setStartTime('00:00');
setEndDate(toDateInputValue());
setEndTime('23:59');
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 | 🟡 Minor

resetForm에서 endTime'23:59'로 설정되어 TimePicker 5분 단위 도메인과 충돌합니다.

초기값(line 26)은 '23:55'로 올바르게 설정되어 있지만, resetForm에서는 '23:59'로 리셋됩니다. 일관성을 위해 동일한 값을 사용해야 합니다.

🔧 수정 제안
  const resetForm = () => {
    setTitle('');
    setStartDate(toDateInputValue());
    setStartTime('00:00');
    setEndDate(toDateInputValue());
-   setEndTime('23:59');
+   setEndTime('23:55');
    setLocation('');
    setContent('');
  };
📝 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
setEndTime('23:59');
const resetForm = () => {
setTitle('');
setStartDate(toDateInputValue());
setStartTime('00:00');
setEndDate(toDateInputValue());
setEndTime('23:55');
setLocation('');
setContent('');
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/modal/CreateScheduleModal.tsx` at line 35,
resetForm currently calls setEndTime('23:59'), which conflicts with the
TimePicker's 5-minute step and the component's initial endTime value of '23:55';
update resetForm to use '23:55' instead (or better, reference a single
DEFAULT_END_TIME constant used by the initial state) so resetForm, the initial
state, and the TimePicker step are consistent (refer to resetForm and setEndTime
in CreateScheduleModal).

handleClose();
};

const isValid = title.trim().length > 0 && startDate < endDate && cardinalNumber !== null;
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

같은 날짜의 일정을 생성할 수 없습니다.

현재 startDate < endDate 비교는 같은 날짜인 경우(예: 오전 10시~오후 2시) 항상 false를 반환합니다. 시간을 포함한 비교가 필요합니다.

🔧 수정 제안
- const isValid = title.trim().length > 0 && startDate < endDate && cardinalNumber !== null;
+ const isValid = (() => {
+   if (!title.trim() || cardinalNumber === null) return false;
+   const start = new Date(`${startDate}T${startTime}`);
+   const end = new Date(`${endDate}T${endTime}`);
+   return start < end;
+ })();
📝 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
const isValid = title.trim().length > 0 && startDate < endDate && cardinalNumber !== null;
const isValid = (() => {
if (!title.trim() || cardinalNumber === null) return false;
const start = new Date(`${startDate}T${startTime}`);
const end = new Date(`${endDate}T${endTime}`);
return start < end;
})();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/modal/CreateScheduleModal.tsx` at line 51,
isValid currently uses startDate < endDate which can fail to consider time
components (or compare non-Date values); update the validation to compare
timestamps instead by using startDate.getTime() < endDate.getTime() (or, if
startDate/endDate may be strings, first parse them to Date and then compare
.getTime()), keeping the other checks (title.trim() and cardinalNumber !== null)
and referencing the isValid expression and the startDate/endDate/cardinalNumber
variables.

Comment on lines +78 to +82
const handleSubmit = () => {
if (!form.title.trim()) return;
// TODO: API 연동 시 수정 요청
handleClose();
};
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

저장이 수정값을 반영하지 않고 모달만 닫습니다.

현재 handleSubmit()은 제목만 확인한 뒤 바로 닫기 때문에, 사용자가 바꾼 값이 부모 목록이나 서버 어디에도 반영되지 않습니다. 결과적으로 “저장”이 실제로는 discard와 거의 같은 동작이 됩니다. 최소한 onSave/mutation 성공 이후에만 닫히도록 연결이 필요합니다.

수정 방향 예시
 interface EditScheduleModalProps {
   open: boolean;
   onOpenChange: (open: boolean) => void;
   schedule: Schedule;
+  onSave: (schedule: Schedule, form: ScheduleFormState) => Promise<void> | void;
   onDelete?: (schedule: Schedule) => void;
 }

-function EditScheduleModal({ open, onOpenChange, schedule, onDelete }: EditScheduleModalProps) {
+function EditScheduleModal({ open, onOpenChange, schedule, onSave, onDelete }: EditScheduleModalProps) {
   const initialForm = toInitialForm(schedule);
   const [form, setForm] = useState<ScheduleFormState>(initialForm);
   const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
   const [discardSource, setDiscardSource] = useState<'close' | 'cancel' | null>(null);

-  const handleSubmit = () => {
+  const handleSubmit = async () => {
     if (!form.title.trim()) return;
-    // TODO: API 연동 시 수정 요청
+    await onSave(schedule, form);
     handleClose();
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/schedule/modal/EditScheduleModal.tsx` around lines 78 -
82, handleSubmit currently only validates title and calls handleClose, so
changes never propagate; update it to call the parent save handler or API
mutation (e.g., an onSave prop or updateSchedule mutation) with the current form
state, await the result, and only call handleClose on success; also surface
errors (keep modal open on failure) and toggle a local saving flag (e.g.,
isSaving) to disable the save button while the request is in flight; reference
the handleSubmit function, handleClose, the form state (form.title and other
fields), and the parent onSave/updateSchedule mutation to locate the changes.

@JIN921 JIN921 merged commit 27f9065 into develop Apr 14, 2026
5 checks passed
@JIN921 JIN921 deleted the feat/WTH-255-어드민-일정-세션관리-UI-구현 branch April 14, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants