Skip to content

Conversation

@Hwanggyuun
Copy link
Contributor

@Hwanggyuun Hwanggyuun commented Aug 23, 2025

📌 Related Issues

✅ 체크 리스트

  • PR 제목의 형식을 잘 작성했나요? e.g. [Feat/#이슈번호] PR 템플릿 작성
  • 빌드가 성공했나요? (pnpm build)
  • 컨벤션을 지켰나요?
  • 이슈는 등록했나요?
  • 리뷰어와 라벨을 지정했나요?

📄 Tasks

my page 완료했습니다

  • 일정 수정, 추가 모달까지 완료했습니다.

⭐ PR Point (To Reviewer)

Schedule 부분은 동희님의 시간표를 많이 착안해서 만들었습니다.
그 과정에서 여러 함수가 들어가 util로 많이 빼놨습니다.

그리고 모달 부분도 최대한 api 를 전달하기 쉬운 형태로 만들어놨습니다.

아 그리고 api-data 부분을 수정했는데
example 이랑 실제 들어오는 것이랑 달라서 고민을 하다가 백엔드 분에게 물어봤는데 '0' 같은 형태로 오는게 맞다고 하셔서 그렇게 임의로 수정했습니다.

📷 Screenshot

스크린샷 2025-08-22 오후 5 16 39 스크린샷 2025-08-22 오후 5 16 48 스크린샷 2025-08-22 오후 5 16 54 스크린샷 2025-08-23 오후 7 48 33 스크린샷 2025-08-23 오후 7 48 40

🔔 ETC

Summary by CodeRabbit

  • 신기능
    • 마이페이지에 주간 일정표 추가 및 일정 추가/수정 모달(선택·시간 입력 포함) 지원.
    • 프로필 헤더 컴포넌트 추가.
  • UI/UX
    • 팝업 오버레이에 진한 딤 레이어 적용 및 바깥 영역 클릭으로 닫기 동작 개선.
    • 일정표에 가로 스크롤, 색상 하이라이트 및 요일/시간 선택 UI 추가.
  • 에셋
    • 플러스(검정), 공유(검정), 닫기 X(회색) 아이콘 추가.
  • Chores
    • 공개 API: 일정의 dayOfWeek 값 형식이 문자열 숫자('0'–'6')로 변경되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Aug 23, 2025

Walkthrough

마이페이지에 일정표 UI, 일정 추가/수정 모달 및 관련 컴포넌트·유틸·스타일이 추가되었고, 설정용 ProfileHeader가 도입되었습니다. PopupOverlay의 구조와 동작이 변경되었으며, API의 SchedulesDTO.dayOfWeek 타입이 문자열 숫자('0'–'6')로 변경되었습니다.

Changes

Cohort / File(s) Summary
API 계약 변경
api/data-contracts.ts
SchedulesDTO.dayOfWeek 타입을 요일 리터럴("MONDAY""SUNDAY")에서 문자열 숫자 '0''6'로 변경. 여러 타입 별칭 포맷만 단일 행으로 정리(비의미적).
SVG 자산 추가
src/assets/svg/index.ts
IcPlusBlack, IcShareBlack, IcNavXGray 신규 export 추가.
마이페이지 도입
src/pages/myPage/MyPage.tsx, src/pages/myPage/MyPage.css.ts, src/pages/myPage/constant/DefaultSchedule.ts
마이페이지에 일정 섹션과 상태/핸들러(모달 열기/추가/수정) 추가, 기본 일정 상수 및 스타일 추가.
설정 프로필 헤더
src/pages/setting/component/ProfileHeader.tsx, .../ProfileHeader.css.ts
ProfileHeader 컴포넌트 및 스타일 추가.
PopupOverlay 리워크
src/shared/components/popupOverlay/PopupOverlay.tsx, .../PopupOverlay.css.ts
dim 레이어 분리, 컨테이너 클릭으로 닫기(onClose), open 기본값 제거, DOM 구조·z-index 조정.
일정표 컴포넌트 세트
src/shared/components/schedule/*.tsx, .../Schedule.css.ts
Schedule, Column, Slot 컴포넌트 및 스타일 추가(7일 컬럼, 슬롯 렌더링, 클릭으로 편집 모달 트리거).
일정 데이터·유틸·타입
src/shared/components/schedule/constant/*, .../util/*, .../type/Schedule.ts
SCHEDULES 더미, 30분 단위 슬롯 생성, 색상 상수, GroupingSchedules, IsScheduled, IsTitleSlot, GetRandomColor 유틸 및 Time 타입 추가.
일정 모달 및 폼
src/shared/components/scheduleModal/*
ScheduleModal, ScheduleSelectField, SelectFieldContent, useScheduleForm(zod+react-hook-form), LocalTimetoString, CSS 추가.
공용 상수
src/shared/constant/Days.ts
ORIGINAL_DAYS (월~일) 상수 추가.

Sequence Diagram(s)

sequenceDiagram
  actor U as 사용자
  participant MP as MyPage
  participant S as Schedule
  participant SM as ScheduleModal
  participant F as useScheduleForm
  participant PO as PopupOverlay

  U->>MP: 일정 추가 버튼 / 슬롯 클릭
  MP->>SM: open=true, schedule(신규/수정) 전달
  SM->>F: 폼 초기값 설정
  SM->>PO: 하단 오버레이 렌더 (dim + content)
  note over PO: 바깥 클릭 -> onClose\n컨텐츠 클릭 -> stopPropagation
  U->>SM: 필드 선택/입력
  SM->>F: handleChangeField 호출
  U->>SM: 추가/수정 버튼 클릭
  SM->>F: handleSubmit(onSubmit)
  F-->>SM: 유효성 결과
  alt 성공
    SM-->>MP: onClose 호출 (모달 닫힘)
  else 실패
    SM-->>U: 에러 표시
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
컴포넌트 분리 (#102)
설정 페이지에서 profile header 커밋 가지고 오기 (#102)
일정표 컴포넌트 만들기 (#102)
마무리 (#102) 완료 정의가 불명확하여 PR만으로 최종 상태 판단 불가.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
SchedulesDTO.dayOfWeek 타입 변경 (api/data-contracts.ts) 이슈 #102는 마이페이지 컴포넌트 추가가 목적이며 API 스키마 변경 요구가 없음. 공용 계약 변경으로 다른 소비자 영향 가능하므로 이슈 범위 외 변경으로 보임.

Possibly related PRs

Suggested reviewers

  • junghyungo
  • hamxxn

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 457c584 and cba1ccd.

📒 Files selected for processing (1)
  • src/pages/setting/component/ProfileHeader.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/setting/component/ProfileHeader.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#102/my-page

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link

✅ 빌드에 성공했습니다! 🎉

Copy link

@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: 16

🧹 Nitpick comments (48)
src/shared/components/schedule/type/Schedule.ts (1)

1-4: 시간 값의 범위/의도 명시 및 런타임 가드 제안

시간 범위(24h, 분 0–59)를 TSDoc으로 명시하고, 잘못된 값 유입을 빠르게 잡을 수 있도록 가드를 두는 것을 권장합니다.

다음과 같이 주석을 추가해 의도를 드러낼 수 있습니다.

-export interface Time {
-  hour: number;
-  minute: number;
-}
+/**
+ * 24시간제 로컬 시간.
+ * - hour: 0–23
+ * - minute: 0–59
+ */
+export interface Time {
+  hour: number;
+  minute: number;
+}

필요 시 런타임 가드(파일 외부 배치 가능):

export function isValidTime(t: Time): t is Time {
  return Number.isInteger(t.hour) && t.hour >= 0 && t.hour <= 23 &&
         Number.isInteger(t.minute) && t.minute >= 0 && t.minute <= 59;
}
src/pages/myPage/constant/DefaultSchedule.ts (1)

3-9: 싱글턴 객체 노출로 인한 공유 참조/의도치 않은 변이 위험 — 팩토리로 전환 권장

DEFAULT_SCHEDULE를 그대로 상태 초기값 등에 넘기면 참조가 공유되어 변이(side-effect)가 전파될 수 있습니다. 매 사용 시 새로운 객체를 반환하는 팩토리로 바꾸는 것을 권장합니다.

-export const DEFAULT_SCHEDULE: SchedulesDTO = {
-  scheduleId: 0,
-  dayOfWeek: '1',
-  startTime: { hour: 7, minute: 0 },
-  endTime: { hour: 23, minute: 0 },
-  scheduleName: '',
-};
+export const createDefaultSchedule = (): SchedulesDTO => ({
+  scheduleId: 0,
+  dayOfWeek: '1', // 확인 필요: API에서 월=0이면 '0'이 월입니다.
+  startTime: { hour: 7, minute: 0 },
+  endTime: { hour: 23, minute: 0 },
+  scheduleName: '',
+});

사용처에서는 createDefaultSchedule()을 호출해 새로운 객체를 받도록 해주세요.

src/shared/components/schedule/constant/RandomColor.ts (1)

1-1: 팔레트 유니온 타입 노출로 타입 안전성 강화 제안

소비 측에서 색상 문자열 오타를 방지하도록 유니온 타입을 함께 export하면 좋습니다.

-export const blueColors = ['blue3', 'blue4', 'blue5', 'blue6', 'baroBlue'] as const;
+export const blueColors = ['blue3', 'blue4', 'blue5', 'blue6', 'baroBlue'] as const;
+export type BlueColor = typeof blueColors[number];
src/shared/components/schedule/util/IsTitleSlot.ts (1)

4-10: 네이밍 컨벤션(인터페이스 PascalCase, 유틸 함수 camelCase) 정리 제안

  • 인터페이스: isTitleTimePropsIsTitleTimeProps (PascalCase)
  • 유틸 함수: React 컴포넌트가 아니므로 IsTitleSlotisTitleSlot (camelCase) 권장

적용 예:

-interface isTitleTimeProps {
+interface IsTitleTimeProps {
   schedule?: SchedulesDTO;
   startTime: Time;
 }
 
-export default function IsTitleSlot({ schedule, startTime }: isTitleTimeProps): boolean {
+export default function isTitleSlot({ schedule, startTime }: IsTitleTimeProps): boolean {
   return (
     schedule?.startTime?.hour === startTime.hour &&
     schedule?.startTime?.minute === startTime.minute
   );
 }

함수명 변경 시 호출부 업데이트가 필요합니다.

src/shared/components/schedule/constant/ScheduleSlots.ts (2)

1-17: 매직 넘버 제거·타입 명시로 재사용성과 안전성 향상

시작 시각(7시), 간격(30분), 슬롯 수(34) 상수를 도입하고 반환 타입을 명확히 하면 안전성과 가독성이 올라갑니다.

적용 제안(diff):

-export default function ScheduleSlots() {
-  return Array.from({ length: 34 }, (_, index) => {
-    const totalMinutes = index * 30 + 7 * 60;
+type LocalTime = { hour: number; minute: number };
+export type TimeSlot = { startTime: LocalTime; endTime: LocalTime };
+
+const START_HOUR = 7 as const;
+const INTERVAL_MINUTES = 30 as const;
+const SLOT_COUNT = 34 as const;
+
+export default function ScheduleSlots(): TimeSlot[] {
+  return Array.from({ length: SLOT_COUNT }, (_, index) => {
+    const totalMinutes = index * INTERVAL_MINUTES + START_HOUR * 60;
     const hour = Math.floor(totalMinutes / 60);
     const minute = totalMinutes % 60;
     return {
       startTime: {
-        hour: hour,
-        minute: minute,
+        hour,
+        minute,
       },
       endTime: {
         hour: minute === 0 ? hour : hour + 1,
-        minute: minute === 0 ? 30 : 0,
+        minute: minute === 0 ? INTERVAL_MINUTES : 0,
       },
     };
   });
 }

1-17: 타임 슬롯 생성 로직 단일화로 중복 제거 제안

기존 timeTable 계열(예: src/shared/components/timeTable/part/Colum.tsx)의 슬롯 생성 로직과 본 유틸이 사실상 동일합니다. 이 유틸을 공용으로 끌어다 쓰도록 정리하면 유지보수 비용과 오차 가능성이 줄어듭니다.

src/shared/components/scheduleModal/util/LocalTimetoString.ts (3)

1-4: LocalTime 타입 중복 정의를 피하고 API 계약 타입 재사용 권장

api/data-contracts.ts에 이미 LocalTime이 정의되어 있습니다. 동일 개념의 타입을 중복 정의하면 드리프트가 발생할 수 있으므로 기존 타입을 import해서 쓰는 편이 안전합니다.

적용 제안(diff, 경로 alias는 프로젝트 설정에 맞게 조정):

-interface LocalTime {
-  hour?: number;
-  minute?: number;
-}
+import type { LocalTime } from 'api/data-contracts';

6-11: 부분 지정(LocalTime.hour 또는 minute만 존재) 시 처리 정책 명확화

현재는 누락된 값을 0으로 보정하여 "00"을 출력합니다. 입력 불완전 시 빈 문자열을 반환하는 편이 폼 검증/UX 측면에서 더 예측 가능할 수 있습니다. 필요시 아래처럼 완전 지정된 경우에만 문자열을 반환하도록 바꿀 것을 제안합니다.

적용 제안(diff):

 export default function LocalTimetoString(time?: LocalTime): string {
   if (!time) return '';
-  const hourStr = String(time.hour ?? 0).padStart(2, '0');
-  const minuteStr = String(time.minute ?? 0).padStart(2, '0');
+  const { hour, minute } = time;
+  if (hour == null || minute == null) return '';
+  const hourStr = String(hour).padStart(2, '0');
+  const minuteStr = String(minute).padStart(2, '0');
   return `${hourStr}:${minuteStr}`;
 }

6-11: 네이밍 컨벤션 일치(함수/파일: lowerCamelCase) — 호환성 유지용 별칭 제공

유틸 함수는 lowerCamelCase를 선호하는 코드베이스가 많습니다. 바로 이름 변경이 어렵다면, 기본 export는 유지하고 별칭(named export)을 추가해 점진적으로 교체하는 방법을 권장합니다.

적용 예시(파일 하단에 추가):

 export default function LocalTimetoString(time?: LocalTime): string {
   ...
 }
+
+// 점진적 마이그레이션을 위한 별칭
+export const localTimeToString = (time?: LocalTime) => LocalTimetoString(time);
src/shared/components/popupOverlay/PopupOverlay.css.ts (2)

20-20: zIndex 값 타입 일관화(문자열 → 숫자) 및 층위 관계 명시 추천

content.base의 zIndex가 문자열 '1000'입니다. 숫자로 통일하고 dim과의 상대 zIndex를 명시하면 유지보수성이 좋아집니다.

적용 제안(diff):

 export const content = recipe({
-  base: { width: '100%', opacity: '1', zIndex: '1000' },
+  base: { width: '100%', opacity: '1', zIndex: 1000 },
   variants: {

38-44: dim 레이어에 zIndex 명시(=1)로 의도 고정

현재도 content(1000) 위에 올라오지 않지만, 명시적으로 zIndex를 넣어 레이어 순서를 고정해 두는 편이 안전합니다.

적용 제안(diff):

 export const dim = style({
   position: 'absolute',
   inset: '0',
-
   backgroundColor: vars.color.black,
   opacity: '0.8',
+  zIndex: 1,
 });
src/pages/myPage/MyPage.css.ts (2)

8-14: 상단 패딩(9.4rem) 하드코딩 — 안전영역(safe area) 및 헤더 변화 대응을 고려

고정 헤더/노치 디바이스를 고려해 safe-area-inset-top을 포함하거나, 토큰 기반 스페이싱을 사용하는 편이 안전합니다.

적용 제안(diff):

 export const scheduleTitleBox = style({
   display: 'flex',
   justifyContent: 'space-between',
   alignItems: 'center',
-
-  padding: '9.4rem 3rem 2rem 2rem',
+  // 상단 안전영역 + 헤더 높이(예: 6.4rem) 합산. 프로젝트 토큰에 맞게 조정하세요.
+  padding: 'calc(env(safe-area-inset-top, 0px) + 6.4rem) 3rem 2rem 2rem',
 });

다양한 뷰포트(375×812, 390×844 등)에서 헤더/제목이 겹치지 않는지 실기기/시뮬레이터로 한 번만 확인 부탁드립니다.


16-27: 간격/패딩 하드코딩(2rem) → 디자인 토큰화 검토

여러 곳에서 2rem을 반복 사용 중입니다. spacing 토큰(예: space.2)으로 추출하면 추후 디자인 변경 대응이 쉬워집니다.

src/shared/components/schedule/Schedule.css.ts (2)

12-14: Firefox 전용 scrollbarColor만 설정됨 — WebKit 호환 설정 추가 제안

크로스 브라우저 일관성을 위해 WebKit 계열 커스터마이징을 같이 넣어두는 것을 권장합니다.

적용 제안(diff):

-import { style } from '@vanilla-extract/css';
+import { style, globalStyle } from '@vanilla-extract/css';
 ...
 export const scheduleWrapper = style({
   display: 'flex',
   width: '100%',
   overflow: 'auto',
   gap: '1rem',
   scrollbarWidth: 'thin',
   scrollbarColor: `${vars.color.gray0} transparent`,
 });
+
+// WebKit 스크롤바 스타일
+globalStyle(`${scheduleWrapper}::-webkit-scrollbar`, {
+  height: '6px',
+});
+globalStyle(`${scheduleWrapper}::-webkit-scrollbar-thumb`, {
+  backgroundColor: vars.color.gray0,
+  borderRadius: '9999px',
+});

25-31: 슬롯 크기 상수화로 일관성 확보(6.7rem × 3rem)

여러 컴포넌트에서 동일 치수를 사용할 가능성이 높습니다. 상수로 추출해두면 한 곳에서 관리할 수 있습니다.

적용 제안(diff):

-export const slotWrapper = recipe({
+const SLOT_WIDTH = '6.7rem' as const;
+const SLOT_HEIGHT = '3rem' as const;
+export const slotWrapper = recipe({
   base: {
     display: 'flex',
     flexDirection: 'column',
-
-    width: '6.7rem',
-    height: '3rem',
+    width: SLOT_WIDTH,
+    height: SLOT_HEIGHT,
     padding: '0.7rem 0.7rem 0 0.7rem',
src/assets/svg/index.ts (1)

70-72: 새 아이콘 export 3건 추가 적절(LGTM) + 사용 여부/경로만 확인

  • 경로/파일 존재 여부(@svg/ic_plus_black.svg 등)와 빌드 시 alias 설정이 일관적인지만 확인하면 됩니다.
  • 기존 네이밍 규칙(IcNavX, IcNavXGray)과도 충돌 없음.

추가 권장(선택):

  • 아이콘이 실제 사용처가 1~2곳이라면, barrel에서의 일괄 export보다 소비 측 파일에서 직접 import하여 트리 셰이킹 효과를 극대화하는 방안도 고려해보세요.
src/shared/components/schedule/constant/ScheduleContent.ts (1)

3-5: 불필요한 래퍼 타입(GroupedSchedules) 단순화 제안

현재 구조는 SchedulesDTO[]를 한 번 더 감싸는 래퍼입니다. 소비 측에서 굳이 객체 래핑이 필요 없다면 배열 자체를 export하여 API/유틸 시그니처를 단순화하는 게 가독성과 재사용성에 유리합니다.

가능한 변경 예:

-interface GroupedSchedules {
-  schedules: SchedulesDTO[];
-}
-
-export const SCHEDULES: GroupedSchedules = {
-  schedules: [
+export const SCHEDULES: ReadonlyArray<SchedulesDTO> = [
     /* ... */
-  ],
-};
+];
src/pages/setting/component/ProfileHeader.tsx (1)

22-26: 편집 아이콘 접근성/클릭 범위 개선

편집 아이콘(IcWriteGray)이 버튼 역할을 한다면 button 요소로 래핑하고 aria-label을 제공하세요. 아이콘 단독 렌더링은 키보드 접근성과 히트 영역이 부족합니다.

예:

- {isSetting && <IcWriteGray />}
+ {isSetting && (
+   <button type="button" aria-label="프로필 편집" className={styles.editButton}>
+     <IcWriteGray />
+   </button>
+ )}
src/shared/components/schedule/util/GetRandomColor.ts (1)

4-8: 함수명이 'Random'이지만 결정적 매핑 — 네이밍/반환 타입 명시 개선

  • 실제로는 slotIndex + scheduleId 기반의 결정적 색상 선택입니다. 오해를 줄이기 위해 getDeterministicColor 같은 네이밍이 더 적절합니다.
  • 반환 타입이 string으로 넓게 추론됩니다. 팔레트 한정 유니온으로 좁혀 두면 오용을 줄일 수 있습니다.

제안:

-import type { SchedulesDTO } from 'api/data-contracts';
-import { blueColors } from '@shared/components/schedule/constant/RandomColor';
+import type { SchedulesDTO } from 'api/data-contracts';
+import { blueColors } from '@shared/components/schedule/constant/RandomColor';

-export default function GetRandomColor(schedule?: SchedulesDTO, slotIndex?: number) {
-  if (slotIndex === undefined || schedule?.scheduleId === undefined) return 'default';
-  const colorIndex = (slotIndex + schedule.scheduleId) % blueColors.length;
-  return blueColors[colorIndex];
-}
+export type BlueColor = (typeof blueColors)[number];
+
+export default function getDeterministicColor(
+  schedule?: SchedulesDTO,
+  slotIndex?: number
+): BlueColor {
+  if (slotIndex === undefined || schedule?.scheduleId === undefined) {
+    return blueColors[0]; // 안전한 기본값
+  }
+  const colorIndex = (slotIndex + schedule.scheduleId) % blueColors.length;
+  return blueColors[colorIndex];
+}
src/shared/components/scheduleModal/components/ScheduleSelectField.tsx (2)

18-20: SelectFieldContent는 컴포넌트 형태로 렌더링하세요

함수 호출 방식({SelectFieldContent({ type })})은 React의 훅/최적화 경로를 우회합니다. JSX 컴포넌트로 사용하는 것이 안전합니다.

-        {SelectFieldContent({ type })}
+        <SelectFieldContent type={type} />

16-20: 접근성(a11y): label과 select를 명시적으로 연결하세요

현재 Text 컴포넌트로만 레이블을 출력하고 있어 스크린리더 호환성이 떨어집니다. useId로 id를 생성하고 label htmlFor로 연결하면 개선됩니다.

 export default function ScheduleSelectField({ label, value, onChange, type }: ScheduleSelectFieldProps) {
   return (
     <div className={styles.selectField}>
-      <Text tag='body_bold_19'>{label}</Text>
-      <select value={value} onChange={onChange} className={clsx(styles.select({ type }), styles.selectText)}>
+      {/*
+        a11y: label-for 연결
+      */}
+      <label htmlFor={React.useId()}>
+        <Text tag='body_bold_19'>{label}</Text>
+      </label>
+      <select
+        id={React.useId()}
+        value={value}
+        onChange={onChange}
+        className={clsx(styles.select({ type }), styles.selectText)}
+      >
         <SelectFieldContent type={type} />
       </select>
     </div>
   )
 }

참고: 동일 렌더마다 서로 다른 useId를 두 번 호출하지 않도록, 함수 상단에서 const selectId = React.useId();로 할당해 label과 select에 동일하게 사용하세요.

src/shared/components/scheduleModal/constants/SelectFieldContent.tsx (2)

24-34: time 옵션의 key는 label을 사용해 의미적 안정성 확보

index 기반 key는 목록 변형 시 예기치 못한 재마운트를 야기할 수 있습니다. 여기서는 label이 고유하므로 key로 쓰는 편이 안전합니다.

-        {ScheduleSlots().map((slot, index) => {
+        {ScheduleSlots().map((slot, index) => {
           const { startTime } = slot;
           const hour = String(startTime.hour).padStart(2, '0');
           const minute = String(startTime.minute).padStart(2, '0');
           const label = `${hour}:${minute}`;
           return (
-            <option key={index} value={label} className={styles.optionText}>
+            <option key={label} value={label} className={styles.optionText}>
               {label}
             </option>
           )
         })}

9-38: 방어적으로 기본 반환값(null) 명시

type이 'day' | 'time' 외로 들어오는 경우는 없겠지만, 런타임 방어 차원에서 마지막에 null을 반환하도록 명시하면 의도가 더 분명해집니다.

 export default function SelectFieldContent({ type }: SelectFieldContentProps) {
   if(type==='day'){
     return(
       <>
         {ORIGINAL_DAYS.map((day, index) => (
           <option key={index} value={index} className={styles.optionText}>
             {`${day}요일`}
           </option>
         ))}
       </>
     )
   }
   else if(type==='time'){
     return(
       <>
         {ScheduleSlots().map((slot, index) => {
           const { startTime } = slot;
           const hour = String(startTime.hour).padStart(2, '0');
           const minute = String(startTime.minute).padStart(2, '0');
           const label = `${hour}:${minute}`;
           return (
             <option key={index} value={label} className={styles.optionText}>
               {label}
             </option>
           )
         })}
       </>
     )
   }
+  return null;
 }
src/shared/components/schedule/util/GroupingSchedules.ts (2)

7-18: 반환 타입 명시로 사용처 타입 안정성 강화

현 구현은 추론되지만, 반환 타입을 명시하면 사용처(예: Column)에 더 명확한 도움을 줍니다.

-export default function GroupingSchedules({ schedules }: GroupedSchedules) {
+export default function GroupingSchedules({ schedules }: GroupedSchedules): SchedulesDTO[][] {
   const grouped: SchedulesDTO[][] = Array.from({ length: 7 }, () => []);

   schedules.forEach(schedule => {
     const index = Number(schedule.dayOfWeek);
     if (index >= 0 && index < 7) {
       grouped[index].push(schedule);
     }
   });

   return grouped;
 }

7-15: 입력 형태 단순화 제안: 배열을 직접 받도록 오버로드/헬퍼 제공

현재는 { schedules } 객체를 인자로 받습니다. 재사용성 향상을 위해 SchedulesDTO[]를 직접 받는 시그니처(또는 헬퍼)를 추가하는 것을 권장합니다.

예시:

export function groupSchedulesArray(schedules: SchedulesDTO[]): SchedulesDTO[][] {
  return GroupingSchedules({ schedules });
}

필요하시면 PR에 포함해 드리겠습니다.

src/shared/components/schedule/part/Slot.tsx (1)

12-13: 빈 슬롯 클릭 시 전달 규약 명시 필요

빈 슬롯에서 {}를 전달해 생성 모달을 열게 하는 암묵적 규약은 타입 상 드러나지 않습니다. Partial 또는 별도 생성 페이로드/콜백(onCreateSlotClick)으로 명확히 분리하는 것을 권장합니다.

원하시면 다음 중 하나로 리팩터 도와드리겠습니다:

  • handleFixSchedule의 시그니처를 (schedule?: SchedulesDTO) => void로 완화하고 빈 슬롯에서는 undefined 전달
  • onEmptySlotClick 별도 콜백을 추가해 생성/수정 동작을 분리
src/shared/components/schedule/Schedule.tsx (1)

12-20: 데이터 주입형으로 변경하여 재사용성 향상 제안

현재 컴포넌트 내부에서 상수 SCHEDULES를 직접 참조합니다. 상위에서 schedules를 prop으로 주입하고, 기본값으로 SCHEDULES를 사용하면 실제 API 연동과 스토리북 모두에 유리합니다. 그룹화 로직은 useMemo로 감싸면 불필요한 재계산을 줄일 수 있습니다.

예시(요약):

-interface ScheduleProps {
-  handleFixSchedule: (_schedule: SchedulesDTO) => void;
-}
+interface ScheduleProps {
+  schedules?: SchedulesDTO[];
+  handleFixSchedule: (_schedule: SchedulesDTO) => void;
+}

-export default function Schedule({ handleFixSchedule }: ScheduleProps) {
-  const grouped = GroupingSchedules( SCHEDULES );
+export default function Schedule({ schedules, handleFixSchedule }: ScheduleProps) {
+  const grouped = React.useMemo(
+    () => GroupingSchedules({ schedules: schedules ?? SCHEDULES.schedules }),
+    [schedules]
+  );
src/pages/myPage/MyPage.tsx (3)

15-17: 모달 토글은 함수형 업데이트로 안전하게 전환하세요

현재 상태값을 직접 캡처해 반전하고 있어 비동기 업데이트 경쟁 시 예기치 않은 토글 실패가 생길 수 있습니다. 함수형 업데이트로 교체를 권장합니다.

-  const handleModal = () => {
-    setOpenSchedule(!openSchedule);
-  }
+  const handleModal = () => {
+    setOpenSchedule(prev => !prev);
+  };

14-14: 모달 대상 상태 타입을 명확히 (선언적) 표현

useState<SchedulesDTO>({})는 “항상 객체”라는 신호를 주어 추가/수정 구분을 흐립니다. SchedulesDTO | null(혹은 undefined)로 선언하면 상태 의미가 명확해지고, 이후 로직 단순화(예: isEdit = !!modalSchedule && modalSchedule.scheduleId! > 0)가 가능합니다.

-  const [modalSchedule, setModalSchedule] = useState<SchedulesDTO>({});
+  const [modalSchedule, setModalSchedule] = useState<SchedulesDTO | null>(null);

32-34: 클릭 가능한 SVG 아이콘의 접근성 보완

현재 SVG에 직접 onClick을 바인딩하고 있어 키보드 접근이 불가합니다. role/tabIndex 및 keydown 핸들러를 추가하거나 버튼 래퍼를 사용하세요.

-        <IcPlusBlack className={styles.optionSvg} onClick={handleAddSchedule}/>
-        <IcShareBlack className={styles.optionSvg} />
+        <IcPlusBlack
+          className={styles.optionSvg}
+          onClick={handleAddSchedule}
+          role="button"
+          tabIndex={0}
+          onKeyDown={(e) => e.key === 'Enter' && handleAddSchedule()}
+          aria-label="일정 추가"
+        />
+        <IcShareBlack
+          className={styles.optionSvg}
+          role="button"
+          tabIndex={0}
+          onKeyDown={(e) => e.key === 'Enter' && /* TODO: 공유 액션 */ void 0}
+          aria-label="일정표 공유"
+        />
src/shared/components/schedule/util/IsScheduled.ts (2)

4-8: 타입/네이밍 컨벤션 정리

interface isScheduleInSlotProps는 파스칼 케이스가 일반적입니다. 내부 전용이지만 팀 컨벤션에 맞춰 정리하면 좋습니다.

-interface isScheduleInSlotProps {
+interface IsScheduleInSlotProps {

그리고 함수 시그니처와 동일하게 파일/함수 네이밍(카멜/파스칼)도 팀 규칙에 맞춰 통일을 고려해 주세요.


10-25: 미세 최적화 및 시그니처 명시

슬롯 시작/종료 총분(min)을 루프 밖에서 한 번만 계산하면 불필요한 반복 계산을 줄일 수 있습니다. 또한 반환 타입을 명시해 사용처가 기대 계약을 쉽게 파악할 수 있게 하세요.

-export default function IsScheduled({ schedules, slotStart, slotEnd }: isScheduleInSlotProps) {
-  for (let i = 0; i < schedules.length; i++) {
+export default function IsScheduled(
+  { schedules, slotStart, slotEnd }: IsScheduleInSlotProps
+): { isInSlot: boolean; slotIndex: number } {
+  const slotStartTotal = slotStart.hour * 60 + slotStart.minute;
+  const slotEndTotal = slotEnd.hour * 60 + slotEnd.minute;
+  for (let i = 0; i < schedules.length; i++) {
     const { startTime, endTime } = schedules[i];
     if (!startTime || !endTime) continue;
 
-    const startTotal = (startTime.hour ?? 0) * 60 + (startTime.minute ?? 0);
-    const endTotal = (endTime.hour ?? 0) * 60 + (endTime.minute ?? 0);
-    const slotStartTotal = slotStart.hour * 60 + slotStart.minute;
-    const slotEndTotal = slotEnd.hour * 60 + slotEnd.minute;
+    const startTotal = (startTime.hour ?? 0) * 60 + (startTime.minute ?? 0);
+    const endTotal = (endTime.hour ?? 0) * 60 + (endTime.minute ?? 0);
     const isInSlot = startTotal < slotEndTotal && endTotal > slotStartTotal;
     if (isInSlot) {
       return { isInSlot: true, slotIndex: i };
     }
   }
   return { isInSlot: false, slotIndex: -1 };
 }
src/shared/components/scheduleModal/ScheduleModal.tsx (2)

33-35: 불필요한 삼항 제거 및 텍스트/액션 isEdit로 통일

라벨/버튼 텍스트와 onClick 분기가 모두 isEdit로 일관되게 표현되도록 정리하세요. 현재 두 버튼의 onClick 삼항은 동일 함수라冗長합니다.

-          <Text tag="body_bold_22">{schedule ? '일정 수정' : '일정 추가'}</Text>
-          <IcNavXGray onClick={onClose} />
+          <Text tag="body_bold_22">{isEdit ? '일정 수정' : '일정 추가'}</Text>
+          <IcNavXGray aria-label="닫기" onClick={onClose} />
-          <Button text={schedule ? "삭제하기" : "취소하기"} size="short" variant="enabled" backgroundColor={vars.color.gray1} onClick={schedule ? onClose : onClose}/>
-          <Button text={schedule ? "수정하기" : "추가하기"} size="short" variant="enabled" backgroundColor={vars.color.baroBlue} onClick={schedule ? handleSubmit(onSubmit) : handleSubmit(onSubmit)}/>
+          <Button
+            text={isEdit ? "삭제하기" : "취소하기"}
+            size="short"
+            variant="enabled"
+            backgroundColor={vars.color.gray1}
+            onClick={onClose}
+          />
+          <Button
+            text={isEdit ? "수정하기" : "추가하기"}
+            size="short"
+            variant="enabled"
+            backgroundColor={vars.color.baroBlue}
+            onClick={handleSubmit(onSubmit)}
+          />

Also applies to: 63-64


59-60: Input value의 제어 컴포넌트 일관성 보장

formData.scheduleName/locationundefined일 때 uncontrolled → controlled 경고가 날 수 있습니다. 빈 문자열로 보정하세요. (동시에 useScheduleForm의 defaultValues에서 공백 기본값을 주는 것도 병행 권장)

-          <InputBar placeholder="약속명을 추가해 주세요." hasBackground={false} borderColor="gray" showMaxLength={true} maxLength={25} value={formData.scheduleName} onChange={handleChangeField("scheduleName")} props={{ style: { color: vars.color.black }}}/>
-          <InputBar placeholder="장소를 추가해 주세요(선택사항)." hasBackground={false} borderColor="gray" leftIcon="location" value={formData.location} onChange={handleChangeField("location")} props={{ style: { color: vars.color.black }}}/>
+          <InputBar placeholder="약속명을 추가해 주세요." hasBackground={false} borderColor="gray" showMaxLength={true} maxLength={25} value={formData.scheduleName ?? ''} onChange={handleChangeField("scheduleName")} props={{ style: { color: vars.color.black }}}/>
+          <InputBar placeholder="장소를 추가해 주세요(선택사항)." hasBackground={false} borderColor="gray" leftIcon="location" value={formData.location ?? ''} onChange={handleChangeField("location")} props={{ style: { color: vars.color.black }}}/>
src/shared/components/scheduleModal/hook/useScheduleForm.ts (3)

37-43: defaultValues에 안전 기본값 제공 (제어 컴포넌트 및 밸리데이션 일관성)

빈 문자열 기본값을 부여해 초기 렌더에서 controlled 컴포넌트를 보장하고, 선택 전 상태를 명시합니다.

-    defaultValues: {
-      dayOfWeek,
-      startTime,
-      endTime,
-      scheduleName,
-      location: '',
-    },
+    defaultValues: {
+      dayOfWeek: dayOfWeek ?? '',
+      startTime: startTime ?? '',
+      endTime: endTime ?? '',
+      scheduleName: scheduleName ?? '',
+      location: '',
+    },

47-50: 입력값 전처리: 불필요한 공백 최소화

scheduleName, location은 선행 공백을 제거하면 UX가 좋아집니다. 필드 별 분기 처리를 추가하세요.

-  function handleChangeField(field: keyof ScheduleFormValues) {
-    return function (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) {
-      setValue(field, e.target.value, { shouldDirty: true, shouldValidate: true });
-    };
-  }
+  function handleChangeField(field: keyof ScheduleFormValues) {
+    return function (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) {
+      const raw = e.target.value;
+      const value =
+        field === 'scheduleName' || field === 'location' ? raw.trimStart() : raw;
+      setValue(field, value, { shouldDirty: true, shouldValidate: true });
+    };
+  }

51-53: console.log 잔존

제출 핸들러에 console.log가 남아 있습니다. 실제 API 연동/상태 업데이트 콜백으로 교체하거나 최소한 빌드 전에 제거하세요.

원하시면 useScheduleFormonValid?: (data) => void 콜백을 주입받도록 확장하는 패치도 제공하겠습니다.

src/shared/components/scheduleModal/ScheduleModal.css.ts (5)

58-70: select(base)는 인터랙션 요소임을 드러내고 키보드 포커스 링을 제공하세요

현재 레시피의 base 스타일에는 커서·포커스 처리(접근성)가 없습니다. 클릭 가능한 필드로 사용된다면 시각적 affordance와 focus-visible 처리가 필요합니다.

적용 예(포커스 링/포인터 커서 추가):

 export const select = recipe({
   base: {
     width: '10.4rem',
     padding: '0.9rem 2rem',

     borderRadius: '1rem',

-    background: vars.color.white,
+    background: vars.color.white,
     backgroundRepeat: 'no-repeat',
     backgroundPosition: 'right 1.2rem center',
     boxShadow: '0 0 8px 0 rgba(0, 0, 0, 0.05)',
+    cursor: 'pointer',
+    selectors: {
+      '&:focus-visible': {
+        outline: `2px solid ${vars.color.baroBlue}`,
+        outlineOffset: '2px',
+      },
+    },
   },

추가로, 이 요소가 div/span 등 비-포커스 가능 엘리먼트라면 TSX 쪽에서 tabIndex={0} 또는 button 역할 부여도 함께 고려해 주세요.


60-62: 고정 폭으로 인한 텍스트 잘림 가능성 – 최소 폭 + 말줄임 처리 권장

요일/시간 레이블 길이에 따라 10.4rem 고정 폭이 깨질 수 있습니다. 가변 폭 + 최소 폭, 말줄임을 권장합니다.

-    width: '10.4rem',
+    minWidth: '10.4rem',
+    width: 'auto',
+    whiteSpace: 'nowrap',
+    overflow: 'hidden',
+    textOverflow: 'ellipsis',

65-69: background 대신 backgroundColor 사용 권장

background는 이미지/반복/포지션 등의 속성까지 초기화합니다. 여기서는 색상만 의도되어 보이므로 backgroundColor가 더 안전합니다.

-    background: vars.color.white,
+    backgroundColor: vars.color.white,

82-84: 타이포 토큰 일관성 유지: selectText에도 vars.font 사용

optionTextvars.font.body_16을 사용하지만 selectText는 하드코딩 폰트 크기입니다. 토큰 일관성을 유지해 주세요.

-export const selectText = style({
-  fontSize: '1.6rem',
-});
+export const selectText = style({
+  ...vars.font.body_16,
+});

73-77: 아이콘 경로의 번들링/배포 경로 확인 필요

backgroundImage: url(/icon/...)는 앱의 basePath가 바뀌거나 서브 디렉터리에 배포될 때 깨질 수 있습니다. public 루트 사용이 보장되는지, 또는 빌드 시 자산 경로가 올바르게 재작성되는지 확인해 주세요. 필요 시 절대경로 전략 또는 CSS url()에 빌드 타임 경로 헬퍼를 쓰는 방식을 검토해 주세요.

원하시면 배포 경로(Base URL)에 안전한 자산 경로 전략을 정리해 드리겠습니다.

src/shared/components/schedule/part/Column.tsx (4)

16-16: 가독성 개선: 지역 변수 네이밍 및 구조분해 별칭 적용

SCHEDULESLOTS는 상수처럼 보이지만 런타임 생성 값입니다. 카멜 케이스와 의미 있는 별칭(slotIndexscheduleIndex)으로 혼동을 줄이세요.

-  const SCHEDULESLOTS = ScheduleSlots();
+  const scheduleSlots = ScheduleSlots();
@@
-        {SCHEDULESLOTS.map((scheduleSlot, index) => {
-          const { isInSlot, slotIndex } = isScheduled({ schedules: schedules ?? [], slotStart: scheduleSlot.startTime, slotEnd: scheduleSlot.endTime });
+        {scheduleSlots.map((scheduleSlot, index) => {
+          const { isInSlot, slotIndex: scheduleIndex } = isScheduled({ schedules: schedules ?? [], slotStart: scheduleSlot.startTime, slotEnd: scheduleSlot.endTime });
@@
-              slotIndex={isInSlot ? slotIndex : undefined}
-              schedule={isInSlot ? schedules[slotIndex] : undefined}
+              slotIndex={isInSlot ? scheduleIndex : undefined}
+              schedule={isInSlot ? schedules[scheduleIndex] : undefined}

Also applies to: 21-22, 28-29


1-8: 미세 성능 개선: 슬롯 배열 메모이제이션

매 렌더마다 34개 객체가 생성됩니다. 영향은 작지만 컴포넌트가 자주 리렌더된다면 useMemo로 한 번만 만들도록 할 수 있습니다.

변경 제안(파일 상단 import 및 생성부):

+import { useMemo } from 'react';
@@
-  const scheduleSlots = ScheduleSlots();
+  const scheduleSlots = useMemo(() => ScheduleSlots(), []);

Also applies to: 16-16


22-22: 불필요한 nullish 병합 제거

schedules는 필수 props(SchedulesDTO[])이므로 schedules ?? []는 중복입니다. 간결하게 유지하세요.

-          const { isInSlot, slotIndex: scheduleIndex } = isScheduled({ schedules: schedules ?? [], slotStart: scheduleSlot.startTime, slotEnd: scheduleSlot.endTime });
+          const { isInSlot, slotIndex: scheduleIndex } = isScheduled({ schedules, slotStart: scheduleSlot.startTime, slotEnd: scheduleSlot.endTime });

26-26: key로 index 대신 시간 기반 키 사용 권장

현재는 정적 배열이라 큰 문제는 없지만, 유지보수성 측면에서 의미 있는 키가 안정적입니다.

-              key={index}
+              key={`${scheduleSlot.startTime.hour}-${scheduleSlot.startTime.minute}`}
src/shared/components/popupOverlay/PopupOverlay.tsx (2)

21-21: 대화상자 접근성(a11y) 보강: role/aria 속성 추가

스크린리더 지원을 위해 role="dialog", aria-modal, aria-labelledby를 부여해 주세요. 제목 요소에 id를 주면 됩니다.

-      <div className={styles.container} onClick={onClose}>
+      <div
+        className={styles.container}
+        onClick={onClose}
+        role="dialog"
+        aria-modal="true"
+        aria-labelledby={top && toptitle ? 'popupoverlay-title' : undefined}
+      >
@@
-          <Text tag="body_bold_19" color="white"
-            className={styles.title}>{toptitle}</Text>
+          <Text id="popupoverlay-title" tag="body_bold_19" color="white" className={styles.title}>
+            {toptitle}
+          </Text>

참고: 여러 오버레이가 동시에 뜰 수 있다면 useId로 고유 id를 생성하는 방식이 더 안전합니다(이 변경은 import 라인 등 파일 상단 수정이 필요하므로 별도 커밋으로 진행 권장).

원하시면 useId 기반 예시까지 포함해 패치 PR을 드리겠습니다.

Also applies to: 25-26


15-19: ESC 키로 닫기 지원 제안

오버레이에서 Esc로 닫기 지원을 추가하면 접근성과 UX가 개선됩니다.

예시(참고용 코드 스니펫, import: useEffect 필요):

useEffect(() => {
  if (!open) return;
  const onKeyDown = (e: KeyboardEvent) => {
    if (e.key === 'Escape') onClose();
  };
  window.addEventListener('keydown', onKeyDown);
  return () => window.removeEventListener('keydown', onKeyDown);
}, [open, onClose]);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between df56043 and 457c584.

⛔ Files ignored due to path filters (5)
  • public/icon/ic_arrow_day.svg is excluded by !**/*.svg
  • public/icon/ic_arrow_time.svg is excluded by !**/*.svg
  • src/assets/svg/ic_nav_x_gray.svg is excluded by !**/*.svg
  • src/assets/svg/ic_plus_black.svg is excluded by !**/*.svg
  • src/assets/svg/ic_share_black.svg is excluded by !**/*.svg
📒 Files selected for processing (28)
  • api/data-contracts.ts (4 hunks)
  • src/assets/svg/index.ts (1 hunks)
  • src/pages/myPage/MyPage.css.ts (1 hunks)
  • src/pages/myPage/MyPage.tsx (1 hunks)
  • src/pages/myPage/constant/DefaultSchedule.ts (1 hunks)
  • src/pages/setting/component/ProfileHeader.css.ts (1 hunks)
  • src/pages/setting/component/ProfileHeader.tsx (1 hunks)
  • src/shared/components/popupOverlay/PopupOverlay.css.ts (3 hunks)
  • src/shared/components/popupOverlay/PopupOverlay.tsx (1 hunks)
  • src/shared/components/schedule/Schedule.css.ts (1 hunks)
  • src/shared/components/schedule/Schedule.tsx (1 hunks)
  • src/shared/components/schedule/constant/RandomColor.ts (1 hunks)
  • src/shared/components/schedule/constant/ScheduleContent.ts (1 hunks)
  • src/shared/components/schedule/constant/ScheduleSlots.ts (1 hunks)
  • src/shared/components/schedule/part/Column.tsx (1 hunks)
  • src/shared/components/schedule/part/Slot.tsx (1 hunks)
  • src/shared/components/schedule/type/Schedule.ts (1 hunks)
  • src/shared/components/schedule/util/GetRandomColor.ts (1 hunks)
  • src/shared/components/schedule/util/GroupingSchedules.ts (1 hunks)
  • src/shared/components/schedule/util/IsScheduled.ts (1 hunks)
  • src/shared/components/schedule/util/IsTitleSlot.ts (1 hunks)
  • src/shared/components/scheduleModal/ScheduleModal.css.ts (1 hunks)
  • src/shared/components/scheduleModal/ScheduleModal.tsx (1 hunks)
  • src/shared/components/scheduleModal/components/ScheduleSelectField.tsx (1 hunks)
  • src/shared/components/scheduleModal/constants/SelectFieldContent.tsx (1 hunks)
  • src/shared/components/scheduleModal/hook/useScheduleForm.ts (1 hunks)
  • src/shared/components/scheduleModal/util/LocalTimetoString.ts (1 hunks)
  • src/shared/constant/Days.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (24)
src/shared/components/schedule/type/Schedule.ts (3)
src/shared/components/timeTable/TimeTable.tsx (1)
  • TimeTableProps (6-11)
src/pages/promiseStatus/components/popUp/TimePopUp.tsx (2)
  • TimePopUpProps (11-21)
  • TimePopUp (23-59)
src/shared/components/timeTable/part/Slot.tsx (1)
  • SlotProps (4-8)
src/shared/constant/Days.ts (3)
src/shared/utils/formatDateWithDay.ts (1)
  • formatDateWithDay (3-12)
src/shared/components/timeTable/part/Colum.tsx (1)
  • dateObj (31-35)
src/shared/components/calendar/utils/CalendarUtils.ts (1)
  • DateArray (17-40)
src/shared/components/schedule/Schedule.css.ts (1)
src/shared/styles/theme.css.ts (1)
  • vars (3-64)
src/shared/components/schedule/constant/ScheduleSlots.ts (3)
src/shared/components/timeTable/part/SlotTitle.tsx (2)
  • SlotTitle (4-20)
  • _ (5-10)
src/shared/components/timeTable/part/Colum.tsx (3)
  • _slot (14-30)
  • Column (13-63)
  • timeSlot (43-59)
src/shared/components/timeTable/part/Slot.tsx (1)
  • Slot (10-18)
src/pages/setting/component/ProfileHeader.tsx (2)
api/data-contracts.ts (1)
  • UserProfileSettingResponseDTO (376-392)
src/shared/components/text/Text.tsx (1)
  • Text (57-69)
src/pages/myPage/constant/DefaultSchedule.ts (1)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/pages/myPage/MyPage.tsx (6)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/pages/myPage/constant/DefaultSchedule.ts (1)
  • DEFAULT_SCHEDULE (3-9)
src/shared/components/header/Header.tsx (1)
  • Header (17-46)
src/pages/setting/component/ProfileHeader.tsx (1)
  • ProfileHeader (10-29)
src/shared/components/schedule/Schedule.tsx (1)
  • Schedule (12-26)
src/shared/components/scheduleModal/ScheduleModal.tsx (1)
  • ScheduleModal (18-70)
src/shared/components/schedule/constant/RandomColor.ts (1)
src/shared/components/promiseDetail/utils/getStatusSet.tsx (1)
  • getStatusSet (21-39)
src/shared/components/scheduleModal/ScheduleModal.css.ts (1)
src/shared/styles/theme.css.ts (1)
  • vars (3-64)
src/shared/components/scheduleModal/ScheduleModal.tsx (6)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/shared/components/scheduleModal/util/LocalTimetoString.ts (1)
  • LocalTimetoString (6-11)
src/shared/components/scheduleModal/hook/useScheduleForm.ts (1)
  • useScheduleForm (23-61)
src/shared/components/scheduleModal/components/ScheduleSelectField.tsx (1)
  • ScheduleSelectField (14-23)
src/shared/components/inputBar/InputBar.tsx (1)
  • InputBar (18-79)
src/shared/styles/theme.css.ts (1)
  • vars (3-64)
src/shared/components/schedule/util/GetRandomColor.ts (2)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/shared/components/schedule/constant/RandomColor.ts (1)
  • blueColors (1-1)
src/shared/components/schedule/part/Column.tsx (5)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/shared/constant/Days.ts (1)
  • ORIGINAL_DAYS (3-3)
src/shared/components/schedule/constant/ScheduleSlots.ts (1)
  • ScheduleSlots (1-17)
src/shared/components/text/Text.tsx (1)
  • Text (57-69)
src/shared/components/schedule/part/Slot.tsx (1)
  • Slot (15-23)
src/shared/components/scheduleModal/util/LocalTimetoString.ts (2)
api/data-contracts.ts (1)
  • LocalTime (109-118)
src/shared/components/timeTable/part/Colum.tsx (1)
  • _slot (14-30)
src/shared/components/scheduleModal/hook/useScheduleForm.ts (1)
src/pages/logIn/hook/useLogInForm.ts (1)
  • useLogInForm (13-52)
src/shared/components/scheduleModal/components/ScheduleSelectField.tsx (3)
src/shared/components/text/Text.tsx (1)
  • Text (57-69)
src/shared/components/scheduleModal/ScheduleModal.css.ts (1)
  • select (58-80)
src/shared/components/scheduleModal/constants/SelectFieldContent.tsx (1)
  • SelectFieldContent (9-38)
src/shared/components/schedule/util/IsTitleSlot.ts (2)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/shared/components/schedule/type/Schedule.ts (1)
  • Time (1-4)
src/shared/components/schedule/util/GroupingSchedules.ts (1)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/shared/components/schedule/constant/ScheduleContent.ts (1)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/shared/components/popupOverlay/PopupOverlay.css.ts (2)
src/shared/styles/theme.css.ts (1)
  • vars (3-64)
src/shared/components/popupOverlay/PopupOverlay.stories.tsx (2)
  • PopupOverlayProps (22-27)
  • args (30-58)
src/shared/components/schedule/part/Slot.tsx (5)
src/shared/components/schedule/type/Schedule.ts (1)
  • Time (1-4)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/shared/components/schedule/util/GetRandomColor.ts (1)
  • GetRandomColor (4-8)
src/shared/components/schedule/util/IsTitleSlot.ts (1)
  • IsTitleSlot (9-16)
src/shared/components/text/Text.tsx (1)
  • Text (57-69)
src/shared/components/schedule/util/IsScheduled.ts (2)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/shared/components/schedule/type/Schedule.ts (1)
  • Time (1-4)
src/shared/components/schedule/Schedule.tsx (5)
api/data-contracts.ts (1)
  • SchedulesDTO (425-446)
src/shared/components/schedule/util/GroupingSchedules.ts (1)
  • GroupingSchedules (7-18)
src/shared/components/schedule/constant/ScheduleContent.ts (1)
  • SCHEDULES (7-59)
src/shared/components/timeTable/part/SlotTitle.tsx (1)
  • SlotTitle (4-20)
src/shared/components/schedule/part/Column.tsx (1)
  • Column (14-36)
src/shared/components/scheduleModal/constants/SelectFieldContent.tsx (2)
src/shared/constant/Days.ts (1)
  • ORIGINAL_DAYS (3-3)
src/shared/components/schedule/constant/ScheduleSlots.ts (1)
  • ScheduleSlots (1-17)
src/shared/components/popupOverlay/PopupOverlay.tsx (3)
src/shared/components/popupOverlay/PopupOverlay.css.ts (1)
  • top (46-53)
src/shared/components/text/Text.tsx (1)
  • Text (57-69)
src/shared/components/popupOverlay/PopupOverlay.stories.tsx (4)
  • PopupOverlayProps (22-27)
  • args (30-58)
  • setOpen (53-53)
  • setOpen (38-38)
🔇 Additional comments (20)
src/shared/components/schedule/type/Schedule.ts (1)

1-4: 공용 Time 타입 도입으로 계약 일관성 확보

스케줄 전반에서 시간 표현을 통일한 점 좋습니다. 다른 유틸/파트 간 상호 운용성이 높아집니다.

src/pages/myPage/constant/DefaultSchedule.ts (1)

5-5: 기본 요일(dayOfWeek) 기본값 확인 요청

SchedulesDTO에서 dayOfWeek는 '0'(월요일)~'6'(일요일)로 정의되어 있으며, 샘플 데이터(src/shared/components/schedule/constant/ScheduleContent.ts)에서도 '0'이 월요일, '1'이 화요일로 사용됩니다. 현재 DEFAULT_SCHEDULE의 dayOfWeek가 '1'로 설정되어 있어 화요일이 기본값으로 지정됩니다. 만약 의도하신 기본 요일이 월요일이라면 '0'으로 변경해 주세요. 확인 부탁드립니다.

파일 위치:

  • src/pages/myPage/constant/DefaultSchedule.ts (라인 5: dayOfWeek: '1',)
src/shared/components/schedule/constant/RandomColor.ts (2)

1-1: 색상 팔레트 상수 정의 적절함

as const로 리터럴 타입을 고정한 점 좋습니다. 팔레트 변경 시 타입 안전성이 유지됩니다.


1-1: 토큰 정의 여부 확인 완료
blue3~blue6baroBlue 토큰이 src/shared/styles/theme.css.ts 파일에서 다음과 같이 정의되어 있어 런타임 미적용 이슈가 발생하지 않습니다.

  • blue3: 19번째 줄
  • blue4: 20번째 줄
  • blue5: 21번째 줄
  • blue6: 22번째 줄
  • baroBlue: 25번째 줄
src/shared/components/schedule/util/IsTitleSlot.ts (1)

1-2: LocalTime 필드 호환성 확인 완료
api/data-contracts.ts에서 LocalTime 인터페이스(109–116행)를 확인한 결과 hour?, minute?, second? 세 필드가 모두 정의되어 있어 비교 로직이 항상 false가 되는 문제는 없습니다.

src/shared/components/schedule/constant/ScheduleSlots.ts (2)

1-17: 07:00~24:00, 30분 단위로 34개 슬롯 생성 로직은 타당합니다

슬롯 경계 계산(XX:00→XX:30, XX:30→(XX+1):00)과 마지막 슬롯의 종료 시각(24:00)까지 깔끔하게 떨어집니다. 현재 사용처(컬럼 렌더링/셀렉트 옵션)와도 일관됩니다.


11-14: ScheduleSlots 마지막 슬롯 endTime.hour = 24 발생: 백엔드 호환성 확인 요망

  • 위치

    • 파일: src/shared/components/schedule/constant/ScheduleSlots.ts
    • Array.from({ length: 34 }, …)로 생성된 마지막 인덱스(33)에서
      • startTime = 23:30
      • endTime = 24:00 (hour = minute !== 0 ? hour + 1 = 24, minute = 0)
  • 확인 필요 사항

    1. API 스펙 및 서버 DTO(LocalTime 구현)가 hour 값을 0~23로 제한하는지
    2. 24:00 입력 시 endExclusive 의미로 처리하는지(예: 다음날 00:00으로 해석)
    3. 허용되지 않을 경우 23:59로 보정하거나 전송 단계에서 오류가 발생할 수 있는지
  • 대응 방안 제안

    • 클라이언트에서 endTime.hour === 24일 때 hour = 23, minute = 59로 클램프
    • 또는 24:00을 문자열 "24:00" 형태로 전송해 서버 측에서 파싱 처리
    • 전송 전 유효성 검사 로직에 명시적 예외 처리 추가

위 사항을 백엔드/타입 정의 담당 팀과 조율 후, 적절한 보정 또는 검증 로직을 적용해 주세요.

src/shared/components/scheduleModal/util/LocalTimetoString.ts (2)

6-11: 간결하고 안정적인 포맷팅 로직 👍

값 미지정 시 빈 문자열 반환, 두 자리 포맷팅 모두 일관적입니다. 모달 초기값 세팅 용도로 적합해 보입니다.


6-11: 24:00 포맷팅 처리 방침 확인 필요

LocalTimetoString 함수는 hour=24, minute=0인 경우에도 별도 예외 없이 "24:00"으로 포맷팅하며, 코드베이스 내에 "24:00" 리터럴 사용처는 발견되지 않았습니다.
백엔드·UI 계약에 따라 아래 사항을 꼭 검토하고 방침을 결정해 주세요:

  • 24:00 출력 허용 여부
  • 불허 시
    • UI 표시만 "24:00"으로 유지
    • 서버 전송 시 "23:59"로 보정
  • LocalTime 타입 정의(예: api/data-contractsSchedulesDTO)에서 hour/minute 범위 제약 문서화 또는 유효성 검사 로직 추가
  • 정책 확정 후 관련 단위 테스트 케이스 추가

점검 대상 위치:

  • src/shared/components/scheduleModal/util/LocalTimetoString.ts (함수 정의)
  • src/shared/components/scheduleModal/ScheduleModal.tsx 20–21행 (LocalTimetoString 호출부)

확인 부탁드립니다.

src/shared/components/popupOverlay/PopupOverlay.css.ts (2)

7-7: inset: 0로 통합한 포지셔닝 적용 좋습니다

top/left/right/bottom 개별 지정보다 간결하고 의도도 명확합니다.


5-17: 클릭 전파 로직 확인 완료
PopupOverlay.tsx 내 DOM 구조와 핸들러 배치가 의도대로 구현된 것을 확인했습니다.

  • <div className={styles.container} onClick={onClose}>
    컨테이너에 클릭 시 닫기 핸들러(onClose)가 붙어 있어 자식 요소에서 이벤트 전파가 멈추지 않으면 팝업이 닫힙니다.
  • <div className={styles.dim} />
    dim 영역은 별도의 stopPropagation 처리가 없어 클릭 시 컨테이너의 onClose가 호출됩니다.
  • <div className={styles.content({position})} onClick={(e) => e.stopPropagation()}>
    content 영역은 클릭 시 이벤트 전파를 중단하기 때문에 팝업이 유지됩니다.

의도한 “dim 클릭 → 팝업 닫힘, content 클릭 → 팝업 유지” 시나리오가 정확히 구현되어 있어 추가 수정이 필요 없습니다.

src/pages/myPage/MyPage.css.ts (1)

3-7: 페이지 컨테이너의 컬럼 레이아웃은 단순·명확합니다

MyPage의 상하 구조와 이후 섹션 확장에 적합한 기본기입니다.

src/shared/components/schedule/Schedule.css.ts (3)

5-14: 수평 스크롤·간격·스크롤바 스타일 정의 적절

모바일에서 주간 7열을 담기에 합리적인 설정입니다.


56-58: 제목 슬롯 z-index(10)는 충분하나, 상위 컨텍스트와의 충돌 가능성은 낮습니다

현재 컨테이너 계층(z-index: 9999 등)과 비교 시 로컬 컨텍스트에서만 적용되므로 문제는 없어 보입니다. 현 상태 유지해도 괜찮습니다.


49-53: pointerEvents: 'none'로 default 슬롯 클릭이 비활성화되는지 확인 필요

아래 위치를 확인해 주세요:

  • src/shared/components/schedule/Schedule.css.tsdefault variant에서 pointerEvents: 'none'으로 설정되어 있습니다.
  • src/shared/components/schedule/part/Slot.tsx 19행: <div className={styles.slotWrapper(...)} onClick={...}>에 클릭 핸들러가 바인딩되어 있으나, default 상태의 슬롯은 pointerEvents 설정으로 클릭 이벤트가 전달되지 않습니다.

의도대로 빈 슬롯 클릭/드래그로 일정 추가·선택 UX가 동작하는지 검토하고, 필요하다면 default variant의 pointerEvents 설정을 제거하거나 클릭 가능한 variant를 별도로 분리해 주세요.

api/data-contracts.ts (1)

1154-1154: 타입 별칭 포맷 변경(LGTM)

해당 네 타입 별칭은 단순 포맷 변경으로 보이며 의미론적 변화는 없습니다. 생성 파일의 일관성 유지 측면에서 문제 없습니다.

Also applies to: 1200-1200, 1216-1216, 1218-1218

src/shared/components/schedule/Schedule.tsx (1)

18-23: 요일 인덱스-레이블 매핑 재확인 요청

ORIGINAL_DAYS가 월일(06) 기준이고 dayOfWeek도 '0'~'6'으로 맞춰져 있습니다. 현재 Monday-first 기준 렌더가 의도와 일치하는지(특히 서버 데이터도 동일 기준인지) 최종 확인해 주세요.

확인 포인트:

  • 서버 dayOfWeek=‘0’이 월요일이라는 백엔드 컨펌과 일치하는지
  • 타임테이블 헤더/슬롯 정렬이 UI 시안과 동일한지
src/pages/setting/component/ProfileHeader.css.ts (1)

3-24: 스타일 토큰 구성 깔끔합니다

레이아웃/간격 설정이 명확하고 컴포넌트 사용처와 일관됩니다. 별도 이슈 없습니다.

src/shared/components/schedule/part/Column.tsx (1)

15-15: 요일 인덱스 매핑 확인 필요: dayOfWeek → ORIGINAL_DAYS

ORIGINAL_DAYS[Number(dayOfWeek)]에서 '0'이 '월'로 매핑됩니다. 백엔드에서 '0''6'이 월일 순으로 정의되어 있는지 재확인해 주세요. 불일치 시 전 주간 전반의 타이틀이 잘못 표시됩니다.

src/shared/components/popupOverlay/PopupOverlay.tsx (1)

15-19: 기본값 변경 영향 확인: open 기본 true 제거

이전 구현에서 open = true였다면, 호출부 중 open을 생략하던 곳이 있었다면 이제 표시되지 않습니다. 모든 사용처가 명시적으로 open을 전달하는지 확인해 주세요.

| "FRIDAY"
| "SATURDAY"
| "SUNDAY";
dayOfWeek?: '0' | '1' | '2' | '3' | '4' | '5' | '6';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

근데 백엔드에서 저렇게 오는게 맞다고 했어..

Copy link

Choose a reason for hiding this comment

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

@Hwanggyuun 알겠습니다! 백엔드에서 확인해주신 정보 감사합니다.

요청 시에는 dayOfWeek: number, 응답 시에는 dayOfWeek: '0'|'1'|...|'6' 형태로 의도적으로 설계된 것이군요. 제가 타입 불일치를 버그로 판단했는데, 실제로는 백엔드에서 의도한 스펙이었네요.

이전 제안은 무시해주시면 됩니다. 현재 구현이 백엔드 스펙에 맞게 올바르게 되어 있습니다.


✏️ Learnings added
Learnt from: Hwanggyuun
PR: KUIT-BARO/BARO-FRONTEND#108
File: api/data-contracts.ts:441-441
Timestamp: 2025-09-05T10:19:46.098Z
Learning: In the BARO project's API, dayOfWeek field intentionally uses different types for requests vs responses: AddScheduleRequestDTO.dayOfWeek expects number type, while SchedulesDTO.dayOfWeek returns string union ('0'|'1'|'2'|'3'|'4'|'5'|'6'). This is confirmed as correct by the backend team and should not be treated as a type mismatch issue.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

Choose a reason for hiding this comment

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

서버에서 배포한 swagger로 받아온 것이여서 해당 파일은 건들면 안됩니다.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines 1 to +3
export const DAYS = ['일', '월', '화', '수', '목', '금', '토'];

export const ORIGINAL_DAYS = ['월', '화', '수', '목', '금', '토', '일'];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

디자인 때문에 한거야 걱정마

| "FRIDAY"
| "SATURDAY"
| "SUNDAY";
dayOfWeek?: '0' | '1' | '2' | '3' | '4' | '5' | '6';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

근데 백엔드에서 저렇게 오는게 맞다고 했어..

@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
Comment on lines 1 to +3
export const DAYS = ['일', '월', '화', '수', '목', '금', '토'];

export const ORIGINAL_DAYS = ['월', '화', '수', '목', '금', '토', '일'];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

디자인 때문에 한거야 걱정마

@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@github-actions
Copy link

github-actions bot commented Sep 5, 2025

✅ 빌드에 성공했습니다! 🎉

@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 5, 2025
Copy link
Contributor

@hamxxn hamxxn left a comment

Choose a reason for hiding this comment

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

현재 slot 같은 컴포넌트를 shared에 또 만드셨는데 원래 있던걸 재사용할 수 있게 리펙토링해서 쓰는건 어떨까요?

| "FRIDAY"
| "SATURDAY"
| "SUNDAY";
dayOfWeek?: '0' | '1' | '2' | '3' | '4' | '5' | '6';
Copy link
Contributor

Choose a reason for hiding this comment

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

서버에서 배포한 swagger로 받아온 것이여서 해당 파일은 건들면 안됩니다.

@KUIT-BARO KUIT-BARO deleted a comment from coderabbitai bot Sep 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 마이 페이지

3 participants