Skip to content

feat: 캘린더 기능 구현 - #35

Merged
JiWoongE merged 1 commit into
developfrom
Feat/#25/calendar-feature
Jul 9, 2026
Merged

feat: 캘린더 기능 구현#35
JiWoongE merged 1 commit into
developfrom
Feat/#25/calendar-feature

Conversation

@JiWoongE

@JiWoongE JiWoongE commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

📝 작업 내용

  • 워크스페이스 캘린더 페이지 라우트 추가
  • FSD 구조 기준으로 calendar entity / feature / view 분리
  • 월간 캘린더 UI 구현
  • 날짜 선택 시 오른쪽 패널에서 해당 날짜 일정 조회 가능하도록 구현
  • 일정 추가 모달 및 로컬 상태 기반 일정 생성 기능 구현
  • 오른쪽 일정 패널에서 일정 개별 삭제 기능 구현
  • 달력 셀에서 일정 2개까지 노출되도록 UI 및 목업 데이터 반영
  • /workspaces/test/calendar 진입을 위한 test workspace mock 추가

📌 주요 변경 사항

  • src/app/workspaces/[workspaceId]/calendar/page.tsx
    • 캘린더 페이지 라우트 추가
  • src/views/calendar
    • 캘린더 페이지 조합 레이어 추가
  • src/features/manage-calendar
    • 캘린더 월간 뷰, 일정 추가/삭제, 날짜 선택 상태 관리 구현
  • src/entities/calendar-event
    • 일정 타입, 목업 데이터, 일정 칩 UI 추가
  • src/entities/workspace/model/mock-workspace.ts
    • test workspace mock 추가

✅ 확인 사항

  • /workspaces/test/calendar 경로에서 캘린더 화면 진입 가능
  • 날짜 클릭 시 오른쪽 패널에 선택한 날짜 일정 표시
  • 일정 추가 시 달력 셀과 오른쪽 패널에 반영
  • 오른쪽 패널에서 일정 개별 삭제 가능
  • 달력 셀에서 일정 최대 2개까지 노출

🧪 테스트

  • npm run lint
  • npm run typecheck
  • npm run build

📷 스크린샷

캘린더 메인 화면

image

일정 추가 모달

image

🔗 관련 이슈

Summary by CodeRabbit

  • New Features

    • 워크스페이스별 캘린더 페이지가 추가되어, 각 워크스페이스의 일정을 볼 수 있게 되었습니다.
    • 월별 달력 화면에서 날짜별 일정 확인, 일정 추가, 일정 삭제가 가능합니다.
    • 일정은 색상 칩으로 표시되며, 긴 제목도 보기 좋게 정리됩니다.
  • Bug Fixes

    • 워크스페이스가 없을 때도 빈 일정 목록으로 안정적으로 표시됩니다.
    • 기본 테스트 워크스페이스와 일정 데이터가 추가되어 초기 화면이 더 자연스럽게 동작합니다.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

캘린더 이벤트 도메인 타입/mock 데이터, 워크스페이스별 캘린더 조회를 위한 Zustand 스토어, 월간 그리드 유틸리티, 일정 표시/추가 UI(CalendarView, CalendarEventChip), 그리고 /workspaces/[workspaceId]/calendar 라우트를 연결하는 CalendarPage가 새로 추가되었습니다.

Changes

캘린더 기능

Layer / File(s) Summary
캘린더 이벤트 엔티티 및 mock 데이터
src/entities/calendar-event/model/calendar-event.types.ts, src/entities/calendar-event/model/mock-calendar-events-by-workspace.ts, src/entities/calendar-event/ui/CalendarEventChip.tsx, src/entities/calendar-event/index.ts, src/entities/workspace/model/mock-workspace.ts
CalendarEvent, CalendarEventColor, CalendarEventFormValues 타입과 워크스페이스별 mock 이벤트 조회 함수, 색상별 이벤트 칩 UI, barrel export를 추가하고, mockWorkspacesByIdtest 워크스페이스 항목을 추가했습니다.
캘린더 유틸리티와 상태 스토어
src/features/manage-calendar/model/calendar-utils.ts, src/features/manage-calendar/model/use-calendar-events-store.ts
월간 그리드 생성, 월 라벨 생성, ISO 날짜 포맷 등 유틸리티와 워크스페이스별 이벤트를 관리하는 Zustand 스토어(initializeWorkspace, addCalendarEvent, removeCalendarEvent)를 추가했습니다.
CalendarView UI 및 feature export
src/features/manage-calendar/ui/CalendarView.tsx, src/features/manage-calendar/index.ts
월 이동, 날짜 셀 그리드, 선택 날짜 사이드바, 일정 추가 모달(제목/시간/색상 입력 및 유효성 검사)을 포함한 CalendarView 컴포넌트와 feature index export를 추가했습니다.
캘린더 페이지 및 라우트 연결
src/views/calendar/ui/CalendarPage.tsx, src/views/calendar/index.ts, src/app/workspaces/[workspaceId]/calendar/page.tsx
workspaceId를 받아 CalendarView에 전달하는 CalendarPage와 그 barrel export, 그리고 라우트 params를 await해 workspaceId를 추출하는 앱 라우트 페이지를 추가했습니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CalendarView
  participant useCalendarEventsStore
  participant CalendarEventChip

  User->>CalendarView: 날짜 선택 및 "일정 추가" 클릭
  CalendarView->>CalendarView: 모달 오픈, 폼 초기화
  User->>CalendarView: 일정 정보 입력 후 제출
  CalendarView->>CalendarView: 제목 유효성 검사
  CalendarView->>useCalendarEventsStore: addCalendarEvent(workspaceId, event)
  useCalendarEventsStore-->>CalendarView: 상태 업데이트
  CalendarView->>CalendarEventChip: 이벤트 칩 렌더링
Loading

Possibly related PRs

  • TeampleRun/syncly#22: 동일한 src/entities/workspace/model/mock-workspace.tsmockWorkspacesById 목업 데이터를 수정하는 변경입니다.

Suggested reviewers: wjswlgh96, 0011810, Kwon812

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 캘린더 기능 구현이라는 핵심 변경을 간결하게 잘 요약한 제목입니다.
Description check ✅ Passed 작업 내용, 테스트, 스크린샷, 관련 이슈가 포함되어 있어 템플릿 요구사항을 대체로 충족합니다.
Linked Issues check ✅ Passed 워크스페이스 캘린더 라우트, 월간 뷰, 일정 추가/삭제, mock 데이터, 재사용 구조가 이슈 #25 요구와 일치합니다.
Out of Scope Changes check ✅ Passed 추가된 파일과 변경 내용이 모두 캘린더 기능과 관련되어 있어 명확한 범위 이탈은 보이지 않습니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Feat/#25/calendar-feature

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/entities/calendar-event/ui/CalendarEventChip.tsx`:
- Around line 4-12: The calendar event chip color mapping is inconsistent with
the color picker values, so selected colors render differently in the chip UI.
Update the `colorClassNames` mapping in `CalendarEventChip` to use the same hex
values as `CalendarView`’s `colorButtonClassNames` and `colorDotClassNames`, and
preferably move these shared color values into a common constant source such as
`calendar-event.types.ts` or a shared constants file. Keep the
`CalendarEventColor` keys aligned so both the modal selection and chip rendering
use the same definitions.

In `@src/features/manage-calendar/model/calendar-utils.ts`:
- Around line 19-36: createCalendarMonthGrid currently hardcodes a 35-cell month
grid, which can truncate the last day for months that need 6 weeks; update the
CalendarDayCell generation in createCalendarMonthGrid to compute the required
cell count dynamically from startDayIndex and the number of days in the month,
and return enough cells to cover the full calendar span. Keep the existing logic
for gridStartDate, isoDate, dayNumber, and isCurrentMonth, but replace the fixed
Array.from length with a calculated value that always includes the last day of
the month.

In `@src/features/manage-calendar/ui/CalendarView.tsx`:
- Line 87: The selectedDate state in CalendarView is not updated when the user
changes months, so actions like add event can target a hidden date. Update the
month navigation handlers in CalendarView (the previous/next month controls) to
also reset selectedDate when currentMonth changes, ideally to the first day of
the newly shown month or today. Reuse or export formatIsoDate from
calendar-utils.ts as needed so the selected date stays in sync with the visible
calendar before triggering the add-event flows from the header and side-panel
buttons.
- Around line 326-359: The form labels in CalendarView are not associated with
their inputs, so update the 일정 이름 and 시간 (선택) fields in CalendarView to use
matching label/input identifiers. Add htmlFor to each label and matching id
attributes to the corresponding input elements in the same form section so
assistive technologies can correctly announce each field’s purpose.
- Around line 35-53: colorButtonClassNames와 colorDotClassNames가 동일한 색상→클래스 매핑을
중복 정의하고 있으니 하나의 공통 맵으로 통합하세요. CalendarView의
colorButtonClassNames/colorDotClassNames 선언을 colorClassNames 같은 단일 상수로 합치고, 해당
상수를 사용하는 모든 렌더링 로직과 참조를 함께 교체해 중복을 제거하세요.
- Around line 308-396: The add-event modal in CalendarView lacks basic
accessibility behavior, so update the modal wrapper to include dialog semantics
such as role="dialog" and aria-modal="true", and make sure the existing close
controls still work. Add Escape-key handling in the CalendarView modal logic so
pressing Escape closes the modal, using the isAddEventOpen / setIsAddEventOpen
flow and the modal container around the form as the main location to wire this
up.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 10ca69c9-3ca9-47c2-bdfc-30e95226657b

📥 Commits

Reviewing files that changed from the base of the PR and between b3d43aa and 80217dc.

📒 Files selected for processing (12)
  • src/app/workspaces/[workspaceId]/calendar/page.tsx
  • src/entities/calendar-event/index.ts
  • src/entities/calendar-event/model/calendar-event.types.ts
  • src/entities/calendar-event/model/mock-calendar-events-by-workspace.ts
  • src/entities/calendar-event/ui/CalendarEventChip.tsx
  • src/entities/workspace/model/mock-workspace.ts
  • src/features/manage-calendar/index.ts
  • src/features/manage-calendar/model/calendar-utils.ts
  • src/features/manage-calendar/model/use-calendar-events-store.ts
  • src/features/manage-calendar/ui/CalendarView.tsx
  • src/views/calendar/index.ts
  • src/views/calendar/ui/CalendarPage.tsx

Comment on lines +4 to +12
const colorClassNames: Record<CalendarEventColor, string> = {
violet: 'bg-[#6b5cff]',
purple: 'bg-[#7f5cff]',
blue: 'bg-[#2f7df6]',
green: 'bg-[#10b74a]',
amber: 'bg-[#ff9f0a]',
coral: 'bg-[#ff6565]',
pink: 'bg-[#eb2f96]',
};

@coderabbitai coderabbitai Bot Jul 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

색상 매핑 불일치: 칩 색상이 선택 UI와 다릅니다.

colorClassNamespurple(#7f5cff)과 blue(#2f7df6) hex 값이 CalendarView.tsxcolorButtonClassNames/colorDotClassNames에서 사용하는 값(#8b5cf6, #3b82f6)과 다릅니다. 사용자가 모달에서 색상을 고르면 실제 칩에는 다른 색으로 렌더링됩니다. 색상 상수를 한 곳(예: calendar-event.types.ts 또는 공용 constants 파일)으로 통합하는 것을 권장합니다.

🎨 제안: 공유 색상 상수로 통합
-const colorClassNames: Record<CalendarEventColor, string> = {
-  violet: 'bg-[`#6b5cff`]',
-  purple: 'bg-[`#7f5cff`]',
-  blue: 'bg-[`#2f7df6`]',
-  green: 'bg-[`#10b74a`]',
-  amber: 'bg-[`#ff9f0a`]',
-  coral: 'bg-[`#ff6565`]',
-  pink: 'bg-[`#eb2f96`]',
-};
+// CalendarView.tsx의 colorButtonClassNames/colorDotClassNames와 동일한 값 사용
+const colorClassNames: Record<CalendarEventColor, string> = {
+  violet: 'bg-[`#6b5cff`]',
+  purple: 'bg-[`#8b5cf6`]',
+  blue: 'bg-[`#3b82f6`]',
+  green: 'bg-[`#10b74a`]',
+  amber: 'bg-[`#ff9f0a`]',
+  coral: 'bg-[`#ff6565`]',
+  pink: 'bg-[`#eb2f96`]',
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const colorClassNames: Record<CalendarEventColor, string> = {
violet: 'bg-[#6b5cff]',
purple: 'bg-[#7f5cff]',
blue: 'bg-[#2f7df6]',
green: 'bg-[#10b74a]',
amber: 'bg-[#ff9f0a]',
coral: 'bg-[#ff6565]',
pink: 'bg-[#eb2f96]',
};
const colorClassNames: Record<CalendarEventColor, string> = {
violet: 'bg-[`#6b5cff`]',
purple: 'bg-[`#8b5cf6`]',
blue: 'bg-[`#3b82f6`]',
green: 'bg-[`#10b74a`]',
amber: 'bg-[`#ff9f0a`]',
coral: 'bg-[`#ff6565`]',
pink: 'bg-[`#eb2f96`]',
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/entities/calendar-event/ui/CalendarEventChip.tsx` around lines 4 - 12,
The calendar event chip color mapping is inconsistent with the color picker
values, so selected colors render differently in the chip UI. Update the
`colorClassNames` mapping in `CalendarEventChip` to use the same hex values as
`CalendarView`’s `colorButtonClassNames` and `colorDotClassNames`, and
preferably move these shared color values into a common constant source such as
`calendar-event.types.ts` or a shared constants file. Keep the
`CalendarEventColor` keys aligned so both the modal selection and chip rendering
use the same definitions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CalendarEventColor 색상 매핑이 CalendarView와 CalendarEventChip에 중복 정의되어 있고, purple/blue의 hex 값이 서로 달라 선택 UI와 실제 칩 색상이 다르게 보일 수 있을 것 같습니다.
색상 매핑을 공용 constants로 분리해서 두 컴포넌트가 동일한 값을 참조하도록 통일하면 좋겠습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline src/features/manage-calendar/ui/CalendarView.tsx --items all
echo "---"
rg -n "ClassNames|#[0-9a-fA-F]{6}" src/features/manage-calendar/ui/CalendarView.tsx src/entities/calendar-event/ui/CalendarEventChip.tsx

Length of output: 5819


🏁 Script executed:

rg -n "colorButtonClassNames\[|colorDotClassNames\[|colorClassNames\[" src/features/manage-calendar/ui/CalendarView.tsx src/entities/calendar-event/ui/CalendarEventChip.tsx

Length of output: 721


Confirmed — there are exactly 3 usage sites to update:

  • src/entities/calendar-event/ui/CalendarEventChip.tsx:23colorClassNames[event.color]
  • src/features/manage-calendar/ui/CalendarView.tsx:267colorDotClassNames[calendarEvent.color]
  • src/features/manage-calendar/ui/CalendarView.tsx:369colorButtonClassNames[color]

The plan already covers all three: replace each with CALENDAR_EVENT_COLOR_CLASS_NAMES[...] after importing the new shared constant from the entities/calendar-event layer (respecting the FSD feature → entity import direction). No other usages were found, so the migration is contained to these two files plus the new constants file and barrel export.


✏️ Learnings added
Learnt from: seongjinss555
Repo: TeampleRun/syncly PR: 35
File: src/entities/calendar-event/ui/CalendarEventChip.tsx:4-12
Timestamp: 2026-07-09T07:24:49.606Z
Learning: In `syncly` repo (Next.js/TypeScript, FSD architecture), calendar event color-to-Tailwind-class mappings previously existed duplicated in `src/entities/calendar-event/ui/CalendarEventChip.tsx` (as `colorClassNames`) and `src/features/manage-calendar/ui/CalendarView.tsx` (as `colorButtonClassNames` and `colorDotClassNames`), with mismatched hex values for `purple`/`blue`. Prefer defining shared `CalendarEventColor` class-name constants once in the `entities/calendar-event` layer (e.g. `calendar-event-colors.ts`) and importing them from `features/manage-calendar` to keep chip, button, and dot colors consistent, per FSD import direction (`feature → entity`).

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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

칩, 색상 선택 버튼, 오른쪽 패널 점 표시가 각각 다른 hex 값을 보던 상태여서 색상 매핑을 calendar-event-colors.ts로 공통화했습니다.
이제 CalendarEventChip, CalendarView 모두 동일한 색상 상수를 사용합니다.

Copy link
Copy Markdown

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 +19 to +36
export function createCalendarMonthGrid(currentMonth: Date): CalendarDayCell[] {
const year = currentMonth.getFullYear();
const monthIndex = currentMonth.getMonth();
const firstDayOfMonth = new Date(year, monthIndex, 1);
const startDayIndex = firstDayOfMonth.getDay();
const gridStartDate = new Date(year, monthIndex, 1 - startDayIndex);

return Array.from({ length: 35 }, (_, index) => {
const date = new Date(gridStartDate);
date.setDate(gridStartDate.getDate() + index);

return {
isoDate: formatIsoDate(date),
dayNumber: date.getDate(),
isCurrentMonth: date.getMonth() === monthIndex,
};
});
}

@coderabbitai coderabbitai Bot Jul 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

월별 셀 개수를 35개로 고정하면 일부 월의 마지막 날짜가 그리드에서 잘립니다.

startDayIndex(월 시작 요일)와 daysInMonth(해당 월 일수) 조합에 따라 35개 셀(5주)로는 월 전체를 표현할 수 없는 경우가 있습니다. 예를 들어 2025년 8월은 금요일(startDayIndex=5)에 시작하고 31일이므로, 8월 31일이 인덱스 35에 위치하게 되어 배열(0~34) 밖으로 밀려나 렌더링되지 않습니다. 31일짜리 월이 금/토에 시작하거나 30일짜리 월이 토요일에 시작하는 모든 경우에 동일한 문제가 발생합니다.

🐛 셀 개수를 동적으로 계산하는 수정안
 export function createCalendarMonthGrid(currentMonth: Date): CalendarDayCell[] {
   const year = currentMonth.getFullYear();
   const monthIndex = currentMonth.getMonth();
   const firstDayOfMonth = new Date(year, monthIndex, 1);
   const startDayIndex = firstDayOfMonth.getDay();
   const gridStartDate = new Date(year, monthIndex, 1 - startDayIndex);
+  const daysInMonth = new Date(year, monthIndex + 1, 0).getDate();
+  const totalCells = Math.ceil((startDayIndex + daysInMonth) / 7) * 7;

-  return Array.from({ length: 35 }, (_, index) => {
+  return Array.from({ length: totalCells }, (_, index) => {
     const date = new Date(gridStartDate);
     date.setDate(gridStartDate.getDate() + index);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function createCalendarMonthGrid(currentMonth: Date): CalendarDayCell[] {
const year = currentMonth.getFullYear();
const monthIndex = currentMonth.getMonth();
const firstDayOfMonth = new Date(year, monthIndex, 1);
const startDayIndex = firstDayOfMonth.getDay();
const gridStartDate = new Date(year, monthIndex, 1 - startDayIndex);
return Array.from({ length: 35 }, (_, index) => {
const date = new Date(gridStartDate);
date.setDate(gridStartDate.getDate() + index);
return {
isoDate: formatIsoDate(date),
dayNumber: date.getDate(),
isCurrentMonth: date.getMonth() === monthIndex,
};
});
}
export function createCalendarMonthGrid(currentMonth: Date): CalendarDayCell[] {
const year = currentMonth.getFullYear();
const monthIndex = currentMonth.getMonth();
const firstDayOfMonth = new Date(year, monthIndex, 1);
const startDayIndex = firstDayOfMonth.getDay();
const gridStartDate = new Date(year, monthIndex, 1 - startDayIndex);
const daysInMonth = new Date(year, monthIndex + 1, 0).getDate();
const totalCells = Math.ceil((startDayIndex + daysInMonth) / 7) * 7;
return Array.from({ length: totalCells }, (_, index) => {
const date = new Date(gridStartDate);
date.setDate(gridStartDate.getDate() + index);
return {
isoDate: formatIsoDate(date),
dayNumber: date.getDate(),
isCurrentMonth: date.getMonth() === monthIndex,
};
});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/manage-calendar/model/calendar-utils.ts` around lines 19 - 36,
createCalendarMonthGrid currently hardcodes a 35-cell month grid, which can
truncate the last day for months that need 6 weeks; update the CalendarDayCell
generation in createCalendarMonthGrid to compute the required cell count
dynamically from startDayIndex and the number of days in the month, and return
enough cells to cover the full calendar span. Keep the existing logic for
gridStartDate, isoDate, dayNumber, and isCurrentMonth, but replace the fixed
Array.from length with a calculated value that always includes the last day of
the month.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

혹시 모를 상황에 대비하여 동적으로 관리하는 것으로 수정하면 좋을 거 같습니다~

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The PR description image (Image 1) shows the Syncly calendar page for "2025년 7월" with a monthly grid (일~토), colored event chips in various day cells, and a right-side detail panel for "2025년 7월 30일" showing an empty-state message ("등록된 일정이 없어요").

The PR description image (Image 2) shows the same calendar with a modal overlay titled "2025년 7월 30일 일정 추가", containing fields for 일정 이름, 시간 (선택), and 색상 (color picker dots), with 취소/추가 buttons at the bottom.

@seongjinss555 네, 동의합니다! 말씀하신 대로 월 시작 요일과 해당 월의 일수에 따라 필요한 셀 개수를 동적으로 계산하는 방식이 더 안전합니다. 위에서 제안한 diff처럼 daysInMonthtotalCells를 계산해서 Array.from({ length: totalCells }, ...)로 바꾸시면 5주(35칸)로 부족한 달(예: 2025년 8월처럼 금요일 시작 + 31일)도 문제없이 렌더링됩니다. 반영해주시면 확인하겠습니다 🙂

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

월 시작 요일과 말일 조합에 따라 5주(35칸)로는 월 전체를 표현할 수 없는 경우가 있어서, 그리드 셀 수를 동적으로 계산하도록 수정했습니다.
이제 startDayIndex + daysInMonth > 35인 경우에는 42칸(6주)으로 렌더링되고, 그 외 월은 기존처럼 35칸을 유지합니다.

Copy link
Copy Markdown

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 +35 to +53
const colorButtonClassNames: Record<CalendarEventColor, string> = {
violet: 'bg-[#6b5cff]',
purple: 'bg-[#8b5cf6]',
blue: 'bg-[#3b82f6]',
green: 'bg-[#10b74a]',
amber: 'bg-[#ff9f0a]',
coral: 'bg-[#ff6565]',
pink: 'bg-[#eb2f96]',
};

const colorDotClassNames: Record<CalendarEventColor, string> = {
violet: 'bg-[#6b5cff]',
purple: 'bg-[#8b5cf6]',
blue: 'bg-[#3b82f6]',
green: 'bg-[#10b74a]',
amber: 'bg-[#ff9f0a]',
coral: 'bg-[#ff6565]',
pink: 'bg-[#eb2f96]',
};

@coderabbitai coderabbitai Bot Jul 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

colorButtonClassNames와 colorDotClassNames가 완전히 중복됩니다.

두 객체가 동일한 색상→클래스 매핑을 갖고 있습니다. 하나로 통합해 재사용하세요.

♻️ 중복 제거 제안
-const colorButtonClassNames: Record<CalendarEventColor, string> = {
-  violet: 'bg-[`#6b5cff`]',
-  purple: 'bg-[`#8b5cf6`]',
-  blue: 'bg-[`#3b82f6`]',
-  green: 'bg-[`#10b74a`]',
-  amber: 'bg-[`#ff9f0a`]',
-  coral: 'bg-[`#ff6565`]',
-  pink: 'bg-[`#eb2f96`]',
-};
-
-const colorDotClassNames: Record<CalendarEventColor, string> = {
-  violet: 'bg-[`#6b5cff`]',
-  purple: 'bg-[`#8b5cf6`]',
-  blue: 'bg-[`#3b82f6`]',
-  green: 'bg-[`#10b74a`]',
-  amber: 'bg-[`#ff9f0a`]',
-  coral: 'bg-[`#ff6565`]',
-  pink: 'bg-[`#eb2f96`]',
-};
+const colorClassNames: Record<CalendarEventColor, string> = {
+  violet: 'bg-[`#6b5cff`]',
+  purple: 'bg-[`#8b5cf6`]',
+  blue: 'bg-[`#3b82f6`]',
+  green: 'bg-[`#10b74a`]',
+  amber: 'bg-[`#ff9f0a`]',
+  coral: 'bg-[`#ff6565`]',
+  pink: 'bg-[`#eb2f96`]',
+};

이후 colorButtonClassNames/colorDotClassNames 사용처를 colorClassNames로 교체하세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const colorButtonClassNames: Record<CalendarEventColor, string> = {
violet: 'bg-[#6b5cff]',
purple: 'bg-[#8b5cf6]',
blue: 'bg-[#3b82f6]',
green: 'bg-[#10b74a]',
amber: 'bg-[#ff9f0a]',
coral: 'bg-[#ff6565]',
pink: 'bg-[#eb2f96]',
};
const colorDotClassNames: Record<CalendarEventColor, string> = {
violet: 'bg-[#6b5cff]',
purple: 'bg-[#8b5cf6]',
blue: 'bg-[#3b82f6]',
green: 'bg-[#10b74a]',
amber: 'bg-[#ff9f0a]',
coral: 'bg-[#ff6565]',
pink: 'bg-[#eb2f96]',
};
const colorClassNames: Record<CalendarEventColor, string> = {
violet: 'bg-[`#6b5cff`]',
purple: 'bg-[`#8b5cf6`]',
blue: 'bg-[`#3b82f6`]',
green: 'bg-[`#10b74a`]',
amber: 'bg-[`#ff9f0a`]',
coral: 'bg-[`#ff6565`]',
pink: 'bg-[`#eb2f96`]',
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/manage-calendar/ui/CalendarView.tsx` around lines 35 - 53,
colorButtonClassNames와 colorDotClassNames가 동일한 색상→클래스 매핑을 중복 정의하고 있으니 하나의 공통 맵으로
통합하세요. CalendarView의 colorButtonClassNames/colorDotClassNames 선언을
colorClassNames 같은 단일 상수로 합치고, 해당 상수를 사용하는 모든 렌더링 로직과 참조를 함께 교체해 중복을 제거하세요.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

하나로 통합하여 관리하는 것이 좋을 거 같습니다. 확인해주세요~

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd CalendarView.tsx src/features/manage-calendar
rg -n 'colorButtonClassNames|colorDotClassNames' src/features/manage-calendar/ui/CalendarView.tsx -C2

Length of output: 1313


@seongjinss555, 확인했습니다. 두 상수(colorButtonClassNames, colorDotClassNames)의 값이 완전히 동일하므로 하나로 통합해도 무방합니다. 아래와 같이 정리하면 됩니다.

-const colorButtonClassNames: Record<CalendarEventColor, string> = {
-  violet: 'bg-[`#6b5cff`]',
-  purple: 'bg-[`#8b5cf6`]',
-  blue: 'bg-[`#3b82f6`]',
-  green: 'bg-[`#10b74a`]',
-  amber: 'bg-[`#ff9f0a`]',
-  coral: 'bg-[`#ff6565`]',
-  pink: 'bg-[`#eb2f96`]',
-};
-
-const colorDotClassNames: Record<CalendarEventColor, string> = {
-  violet: 'bg-[`#6b5cff`]',
-  purple: 'bg-[`#8b5cf6`]',
-  blue: 'bg-[`#3b82f6`]',
-  green: 'bg-[`#10b74a`]',
-  amber: 'bg-[`#ff9f0a`]',
-  coral: 'bg-[`#ff6565`]',
-  pink: 'bg-[`#eb2f96`]',
-};
+const colorClassNames: Record<CalendarEventColor, string> = {
+  violet: 'bg-[`#6b5cff`]',
+  purple: 'bg-[`#8b5cf6`]',
+  blue: 'bg-[`#3b82f6`]',
+  green: 'bg-[`#10b74a`]',
+  amber: 'bg-[`#ff9f0a`]',
+  coral: 'bg-[`#ff6565`]',
+  pink: 'bg-[`#eb2f96`]',
+};

그리고 사용처 2곳을 아래처럼 교체하면 됩니다.

Line 267 (날짜 상세 패널의 색상 점):

-                            className={`mt-1 size-2.5 shrink-0 rounded-full ${colorDotClassNames[calendarEvent.color]}`}
+                            className={`mt-1 size-2.5 shrink-0 rounded-full ${colorClassNames[calendarEvent.color]}`}

Line 369 (모달의 색상 선택 버튼):

-                      className={`size-6 rounded-full ${colorButtonClassNames[color]} ${
+                      className={`size-6 rounded-full ${colorClassNames[color]} ${

이렇게 하면 중복 없이 하나의 매핑으로 버튼과 점 색상을 모두 관리할 수 있습니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

기존의 colorButtonClassNames, colorDotClassNames 중복 매핑은 제거했고, 현재는 calendarEventColorClassNames 공용 상수를 만들어 색상 선택 버튼, 일정 칩, 오른쪽 패널 점 표시가 동일한 매핑을 재사용하고 있습니다.

Copy link
Copy Markdown

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!


const calendarEvents = storedEvents ?? initialEvents;
const [currentMonth, setCurrentMonth] = useState(getInitialCalendarDate);
const [selectedDate, setSelectedDate] = useState('2025-07-30');

@coderabbitai coderabbitai Bot Jul 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

달 이동 시 selectedDate가 갱신되지 않아, 보이지 않는 날짜에 일정이 추가될 수 있습니다.

이전 달/다음 달 버튼(141-166)은 currentMonth만 변경하고 selectedDate(87)는 그대로 유지됩니다. 이 상태에서 상단 "일정 추가"(168-176)나 우측 패널의 "+"(247-254) 버튼을 누르면, 현재 화면에 보이지 않는 이전 달의 selectedDate에 일정이 추가됩니다. 모달 제목에는 정확한 날짜가 표시되지만 사용자가 달을 이동한 사실을 잊으면 의도치 않은 날짜에 일정이 쌓일 수 있습니다.

달 이동 시 selectedDate를 새 달의 첫날(또는 오늘)로 리셋하는 것을 권장합니다.

🐛 수정 제안
                 <button
                   type="button"
                   onClick={() =>
-                    setCurrentMonth(
-                      (current) => new Date(current.getFullYear(), current.getMonth() - 1, 1),
-                    )
+                    setCurrentMonth((current) => {
+                      const next = new Date(current.getFullYear(), current.getMonth() - 1, 1);
+                      setSelectedDate(formatIsoDate(next));
+                      return next;
+                    })
                   }

(next 달 버튼에도 동일하게 적용, formatIsoDatecalendar-utils.ts에서 export 필요)

Also applies to: 141-166, 168-176, 247-254

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/manage-calendar/ui/CalendarView.tsx` at line 87, The
selectedDate state in CalendarView is not updated when the user changes months,
so actions like add event can target a hidden date. Update the month navigation
handlers in CalendarView (the previous/next month controls) to also reset
selectedDate when currentMonth changes, ideally to the first day of the newly
shown month or today. Reuse or export formatIsoDate from calendar-utils.ts as
needed so the selected date stays in sync with the visible calendar before
triggering the add-event flows from the header and side-panel buttons.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

currentMonth와 selectedDate가 분리되어 있어서 달 이동 후 일정 추가 대상 날짜가 현재 화면의 달과 달라질 수 있을 것 같습니다.
사용자가 현재 보고 있는 달 기준으로 일정을 추가한다고 기대할 가능성이 있으니, 이전/다음 달 이동 시 selectedDate도 새 달의 첫날로 갱신하거나, 현재 선택 날짜가 화면에 보이지 않을 때는 일정 추가 버튼의 기준 날짜를 명확히 처리하는 방식이 필요해 보입니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@seongjinss555 말씀하신 방향에 동의합니다. 두 가지 방법 중 하나로 정리하면 될 것 같습니다.

방법 A: 달 이동 시 selectedDate를 새 달 첫날로 갱신 (더 단순)

 <button
   type="button"
   onClick={() =>
-    setCurrentMonth(
-      (current) => new Date(current.getFullYear(), current.getMonth() - 1, 1),
-    )
+    setCurrentMonth((current) => {
+      const next = new Date(current.getFullYear(), current.getMonth() - 1, 1);
+      setSelectedDate(formatIsoDate(next));
+      return next;
+    })
   }
 >

다음 달 버튼에도 동일하게 적용하고, formatIsoDatecalendar-utils.ts에서 export로 변경하면 됩니다.

방법 B: selectedDate는 유지하되, 화면에 보이는 달과 다를 경우 "일정 추가" 진입 시 기준 날짜를 명확히 처리

  • 예: openAddEventModal 호출 시 selectedDatecurrentMonth에 속하지 않으면, 모달 열기 전에 currentMonth의 1일(혹은 오늘)로 대체.
  • 이 방식은 "달만 이동하고 선택 날짜는 유지"하려는 의도가 있을 때 더 안전하지만, 로직이 두 곳(헤더 버튼, 사이드 패널 + 버튼)에 중복될 수 있으니 공통 헬퍼로 뽑는 게 좋겠습니다.

개인적으로는 방법 A가 사용자가 보는 화면과 실제 저장되는 날짜가 항상 일치해서 혼란이 적을 것 같습니다. 다만 "달 이동해도 선택했던 날짜를 유지하고 싶다"는 UX 의도가 있다면 방법 B가 맞을 수 있습니다. 어느 쪽으로 반영하실지 알려주시면 반영 방향에 맞춰 추가로 도와드릴게요.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

이전/다음 달 이동 시 currentMonth만 변경되고 selectedDate가 유지되던 상태여서, 보이지 않는 이전 선택 날짜에 일정이 추가될 수 있었습니다.
달 이동 로직을 changeMonth로 분리하고, 이동한 달의 1일로 selectedDate도 함께 갱신하도록 수정했습니다.

Copy link
Copy Markdown

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 thread src/features/manage-calendar/ui/CalendarView.tsx
Comment thread src/features/manage-calendar/ui/CalendarView.tsx

@seongjinss555 seongjinss555 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

리뷰 참고하시면 좋을 거 같습니다~ 고생하셨습니다~!

Comment on lines +4 to +12
const colorClassNames: Record<CalendarEventColor, string> = {
violet: 'bg-[#6b5cff]',
purple: 'bg-[#7f5cff]',
blue: 'bg-[#2f7df6]',
green: 'bg-[#10b74a]',
amber: 'bg-[#ff9f0a]',
coral: 'bg-[#ff6565]',
pink: 'bg-[#eb2f96]',
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CalendarEventColor 색상 매핑이 CalendarView와 CalendarEventChip에 중복 정의되어 있고, purple/blue의 hex 값이 서로 달라 선택 UI와 실제 칩 색상이 다르게 보일 수 있을 것 같습니다.
색상 매핑을 공용 constants로 분리해서 두 컴포넌트가 동일한 값을 참조하도록 통일하면 좋겠습니다!


const calendarEvents = storedEvents ?? initialEvents;
const [currentMonth, setCurrentMonth] = useState(getInitialCalendarDate);
const [selectedDate, setSelectedDate] = useState('2025-07-30');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

currentMonth와 selectedDate가 분리되어 있어서 달 이동 후 일정 추가 대상 날짜가 현재 화면의 달과 달라질 수 있을 것 같습니다.
사용자가 현재 보고 있는 달 기준으로 일정을 추가한다고 기대할 가능성이 있으니, 이전/다음 달 이동 시 selectedDate도 새 달의 첫날로 갱신하거나, 현재 선택 날짜가 화면에 보이지 않을 때는 일정 추가 버튼의 기준 날짜를 명확히 처리하는 방식이 필요해 보입니다!

@JiWoongE
JiWoongE requested review from 0011810, Kwon812 and wjswlgh96 July 9, 2026 07:28
@JiWoongE
JiWoongE merged commit 1cfb0d3 into develop Jul 9, 2026
1 check passed
@JiWoongE
JiWoongE deleted the Feat/#25/calendar-feature branch July 9, 2026 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 캘린더 기능 구현

2 participants