-
Notifications
You must be signed in to change notification settings - Fork 0
[Feature/#102] my page #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
Walkthrough마이페이지에 일정표 UI, 일정 추가/수정 모달 및 관련 컴포넌트·유틸·스타일이 추가되었고, 설정용 ProfileHeader가 도입되었습니다. PopupOverlay의 구조와 동작이 변경되었으며, API의 SchedulesDTO.dayOfWeek 타입이 문자열 숫자('0'–'6')로 변경되었습니다. Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
✅ 빌드에 성공했습니다! 🎉 |
There was a problem hiding this 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) 정리 제안
- 인터페이스:
isTitleTimeProps→IsTitleTimeProps(PascalCase)- 유틸 함수: React 컴포넌트가 아니므로
IsTitleSlot→isTitleSlot(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/location이undefined일 때 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 연동/상태 업데이트 콜백으로 교체하거나 최소한 빌드 전에 제거하세요.원하시면
useScheduleForm에onValid?: (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 사용
optionText는vars.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 루트 사용이 보장되는지, 또는 빌드 시 자산 경로가 올바르게 재작성되는지 확인해 주세요. 필요 시 절대경로 전략 또는 CSSurl()에 빌드 타임 경로 헬퍼를 쓰는 방식을 검토해 주세요.원하시면 배포 경로(Base URL)에 안전한 자산 경로 전략을 정리해 드리겠습니다.
src/shared/components/schedule/part/Column.tsx (4)
16-16: 가독성 개선: 지역 변수 네이밍 및 구조분해 별칭 적용
SCHEDULESLOTS는 상수처럼 보이지만 런타임 생성 값입니다. 카멜 케이스와 의미 있는 별칭(slotIndex→scheduleIndex)으로 혼동을 줄이세요.- 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.
⛔ Files ignored due to path filters (5)
public/icon/ic_arrow_day.svgis excluded by!**/*.svgpublic/icon/ic_arrow_time.svgis excluded by!**/*.svgsrc/assets/svg/ic_nav_x_gray.svgis excluded by!**/*.svgsrc/assets/svg/ic_plus_black.svgis excluded by!**/*.svgsrc/assets/svg/ic_share_black.svgis 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~blue6및baroBlue토큰이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.tsArray.from({ length: 34 }, …)로 생성된 마지막 인덱스(33)에서
startTime= 23:30endTime= 24:00 (hour = minute !== 0 ? hour + 1 = 24,minute = 0)확인 필요 사항
- API 스펙 및 서버 DTO(LocalTime 구현)가
hour값을 0~23로 제한하는지24:00입력 시endExclusive의미로 처리하는지(예: 다음날 00:00으로 해석)- 허용되지 않을 경우
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-contracts의SchedulesDTO)에서hour/minute범위 제약 문서화 또는 유효성 검사 로직 추가- 정책 확정 후 관련 단위 테스트 케이스 추가
점검 대상 위치:
src/shared/components/scheduleModal/util/LocalTimetoString.ts(함수 정의)src/shared/components/scheduleModal/ScheduleModal.tsx20–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.ts의defaultvariant에서pointerEvents: 'none'으로 설정되어 있습니다.src/shared/components/schedule/part/Slot.tsx19행:<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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
근데 백엔드에서 저렇게 오는게 맞다고 했어..
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
서버에서 배포한 swagger로 받아온 것이여서 해당 파일은 건들면 안됩니다.
There was a problem hiding this comment.
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!
| export const DAYS = ['일', '월', '화', '수', '목', '금', '토']; | ||
|
|
||
| export const ORIGINAL_DAYS = ['월', '화', '수', '목', '금', '토', '일']; |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
근데 백엔드에서 저렇게 오는게 맞다고 했어..
| export const DAYS = ['일', '월', '화', '수', '목', '금', '토']; | ||
|
|
||
| export const ORIGINAL_DAYS = ['월', '화', '수', '목', '금', '토', '일']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
디자인 때문에 한거야 걱정마
|
✅ 빌드에 성공했습니다! 🎉 |
hamxxn
left a comment
There was a problem hiding this 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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
서버에서 배포한 swagger로 받아온 것이여서 해당 파일은 건들면 안됩니다.
📌 Related Issues
✅ 체크 리스트
📄 Tasks
my page 완료했습니다
⭐ PR Point (To Reviewer)
Schedule 부분은 동희님의 시간표를 많이 착안해서 만들었습니다.
그 과정에서 여러 함수가 들어가 util로 많이 빼놨습니다.
그리고 모달 부분도 최대한 api 를 전달하기 쉬운 형태로 만들어놨습니다.
아 그리고 api-data 부분을 수정했는데
example 이랑 실제 들어오는 것이랑 달라서 고민을 하다가 백엔드 분에게 물어봤는데 '0' 같은 형태로 오는게 맞다고 하셔서 그렇게 임의로 수정했습니다.
📷 Screenshot
🔔 ETC
Summary by CodeRabbit