Skip to content

Comments

Style/#139 캘린더 선택날짜#142

Merged
y-eonee merged 4 commits intodevelopfrom
style/#139-캘린더-선택날짜
Jan 21, 2026

Hidden character warning

The head ref may contain hidden characters: "style/#139-\uce98\ub9b0\ub354-\uc120\ud0dd\ub0a0\uc9dc"
Merged

Style/#139 캘린더 선택날짜#142
y-eonee merged 4 commits intodevelopfrom
style/#139-캘린더-선택날짜

Conversation

@soseoyo12
Copy link
Contributor

🔗 연결된 이슈

📄 작업 내용

  • 캘린더 선택 날자로 네비게이트

@soseoyo12 soseoyo12 requested a review from a team January 21, 2026 13:32
@soseoyo12 soseoyo12 self-assigned this Jan 21, 2026
@soseoyo12 soseoyo12 requested review from sum130, wotjs020708 and y-eonee and removed request for a team January 21, 2026 13:32
@soseoyo12 soseoyo12 added 성용👶 Setting 프로젝트 세팅 시 사용 labels Jan 21, 2026
@soseoyo12 soseoyo12 linked an issue Jan 21, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

홈 화면의 예정된 항목 선택을 통해 캘린더 뷰로 네비게이션하고 선택된 날짜를 전달하는 흐름을 구현. HomeCalendarFlowState를 중앙 상태 관리 객체로 도입하여 홈 뷰와 캘린더 뷰 간의 데이터 흐름을 연결. 캘린더 셀 텍스트 색상 로직을 리팩토링하고, 의존성 주입 설정을 업데이트함.

Changes

Cohort / File(s) 변경 사항
홈-캘린더 상태 관리 통합
Cherrish-iOS/.../Feature/Home/HomeCalendarFlowState.swift, Cherrish-iOS/.../Feature/Home/HomeViewModel.swift, Cherrish-iOS/.../Feature/Home/HomeView.swift
HomeCalendarFlowState 신규 생성 (ObservableObject, @Published treatmentDate). HomeViewModel에 homeCalendarFlowState 의존성 추가, selectUpcomingDate 메서드 구현. HomeView의 UpcomingBoxView 항목에 탭 인터랙션 추가 (viewModel.selectUpcomingDate, 캘린더 탭 전환)
캘린더 뷰 상태 동기화
Cherrish-iOS/.../Feature/Calendar/CalendarMain/CalendarView.swift, Cherrish-iOS/.../Feature/Calendar/CalendarMain/CalendarViewModel.swift
CalendarView에 @StateObject var homeCalendarFlowState 추가. onChange, onAppear로 homeCalendarFlowState.treatmentDate를 viewModel과 동기화. CalendarViewModel에 updateDate 메서드 추가 (selectedDate, currentMonth 업데이트, 비동기 데이터 갱신)
의존성 주입 설정 업데이트
Cherrish-iOS/.../PresentationDependencyAssembler.swift, Cherrish-iOS/.../ViewFactory.swift
CalendarTreatmentFlowState, HomeCalendarFlowState 싱글톤 인스턴스 생성 및 DIContainer 등록. HomeViewModel 초기화시 homeCalendarFlowState 전달. ViewFactory의 makeCalendarView에서 homeCalendarFlowState 의존성 해결 추가
캘린더 셀 색상 로직 리팩토링
Cherrish-iOS/.../Feature/Calendar/CalendarMain/CalendarCellView.swift
dayTextColor 계산 속성 신규 추가 (calendarMode .selectedProcedure + downtimeState .none이 아니면 gray1000, D-Day이면 red700, 기본값 gray800)
포매팅
Cherrish-iOS/.../Feature/Treatment/ViewModel/Treatment/TreatmentViewModel.swift
createUserProcedureUseCase.execute 호출 인자를 여러 줄로 분할 (동작 변화 없음)

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant HomeView
    participant HomeViewModel
    participant HomeCalendarFlowState
    participant CalendarView
    participant CalendarViewModel

    User->>HomeView: UpcomingBoxView 항목 탭
    HomeView->>HomeViewModel: selectUpcomingDate(dateString)
    HomeViewModel->>HomeCalendarFlowState: treatmentDate = parsedDate
    HomeView->>HomeView: tabBarCoordinator.switchTab(.calendar)
    
    CalendarView->>HomeCalendarFlowState: homeCalendarFlowState 구독
    HomeCalendarFlowState-->>CalendarView: treatmentDate 변경 감지
    CalendarView->>CalendarViewModel: updateDate(date)
    CalendarViewModel->>CalendarViewModel: selectedDate 업데이트<br/>currentMonth 계산
    CalendarViewModel->>CalendarViewModel: 비동기 데이터 갱신<br/>(procedureCounts, todayProcedureList)
    CalendarView->>HomeCalendarFlowState: treatmentDate = nil (초기화)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Style

Suggested reviewers

  • y-eonee
  • wotjs020708
  • sum130
🚥 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 PR 제목 'Style/#139 캘린더 선택날짜'는 변경 사항의 핵심인 캘린더 선택 날짜 기능 구현과 관련이 있으나, 실제 변경 내용은 홈 뷰에서 캘린더로의 네비게이션 플로우 구현을 포함하고 있어 제목이 전체 범위를 완전히 설명하지 못함.
Description check ✅ Passed PR 설명은 '캘린더 선택 날자로 네비게이트' 내용을 포함하고 있으며 이는 변경 사항과 관련이 있음.
Linked Issues check ✅ Passed Issue #139는 '캘린더 선택날짜 색깔 변경'을 목표로 하며, PR의 변경 사항은 CalendarCellView의 dayTextColor 속성 추가 및 색상 로직 개선, HomeCalendarFlowState를 통한 네비게이션 플로우 구현으로 이 요구사항을 충족함.
Out of Scope Changes check ✅ Passed CalendarCellView 색상 로직 개선, HomeCalendarFlowState 추가, 네비게이션 플로우 구현, DI 설정 변경 등 모든 변경 사항이 Issue #139의 캘린더 선택 날짜 관련 기능 구현과 연관되어 있음.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch style/#139-캘린더-선택날짜

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`:
- Line 27: The property wrapper on homeCalendarFlowState is incorrect for an
externally injected object; replace the `@StateObject` declaration of
homeCalendarFlowState with `@ObservedObject` in CalendarView so the view does not
claim lifecycle ownership (homeCalendarFlowState is provided via
DIContainer/singleton). Update the CalendarView property declaration that
currently reads "@StateObject var homeCalendarFlowState: HomeCalendarFlowState"
to use "@ObservedObject" so the semantics match external injection.
- Around line 69-80: Extract the duplicated logic that checks
homeCalendarFlowState.treatmentDate, calls viewModel.updateDate(date:), and sets
homeCalendarFlowState.treatmentDate = nil into a single helper method (e.g.,
handleTreatmentDate()) inside CalendarView; then replace the bodies of both the
.onChange(of: homeCalendarFlowState.treatmentDate) closure and the .onAppear
closure to call that helper, ensuring you reference the same
viewModel.updateDate(date:) and mutate homeCalendarFlowState.treatmentDate from
within the helper so behavior remains identical.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`:
- Around line 141-148: The code launches detached Tasks in CalendarViewModel
without cancellation, so rapid updateDate calls or view disappearance can spawn
overlapping tasks and stale results; add a stored Task? property (e.g.
fetchTask) on CalendarViewModel, cancel the existing fetchTask before creating a
new Task in updateDate, assign the new Task to fetchTask, and ensure you cancel
fetchTask in deinit or when the view disappears; also handle CancellationError
inside the Task (or check Task.isCancelled) so fetchProcedureCountsOfMonth and
fetchTodayProcedureList results are not applied after cancellation.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/Treatment/TreatmentViewModel.swift`:
- Around line 81-85: 포맷팅 변경이 기능과 직접 관련되지 않으므로, TreatmentViewModel.swift의
createUserProcedureUseCase.execute(...) 호출부에서 여러 줄로 나눈 포맷팅 변경을 별도의 커밋(또는 PR)으로
분리하세요: 현재 변경된 createUserProcedureUseCase.execute 호출을 원래 기능 관련 커밋에서 제거하고 포맷팅 전/후
중 원하는 스타일을 반영한 작은 커밋을 새로 만들어 푸시한 뒤 이 PR은 캘린더 네비게이션 관련 변경만 포함하도록 정리하십시오.

In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift`:
- Around line 41-46: The date parsing in selectUpcomingDate(_:) uses a fixed
format but doesn't set the DateFormatter.locale, calendar, or timeZone, which
can cause failures on some devices; update the DateFormatter in
selectUpcomingDate to set formatter.locale = Locale(identifier: "en_US_POSIX"),
formatter.calendar = Calendar(identifier: .gregorian) and (if backend expects a
specific zone) set formatter.timeZone appropriately before calling
formatter.date(from:), then assign the parsed Date to
homeCalendarFlowState.treatmentDate.

struct CalendarView: View {
@EnvironmentObject private var calendarCoordinator: CalendarCoordinator
@StateObject var viewModel: CalendarViewModel
@StateObject var homeCalendarFlowState: HomeCalendarFlowState
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

@StateObject 대신 @ObservedObject를 사용해야 합니다.

homeCalendarFlowState는 DIContainer에서 외부 생성되어 주입되는 객체입니다. @StateObject는 View가 객체의 생명주기를 소유할 때 사용하고, 외부에서 생성된 객체를 전달받을 때는 @ObservedObject를 사용해야 합니다.

현재 구현에서는 싱글톤으로 등록되어 있어 실질적으로 문제가 발생하지 않을 수 있지만, 의미론적으로 올바른 property wrapper를 사용하는 것이 좋습니다.

🔧 수정 제안
-    `@StateObject` var homeCalendarFlowState: HomeCalendarFlowState
+    `@ObservedObject` var homeCalendarFlowState: HomeCalendarFlowState
📝 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
@StateObject var homeCalendarFlowState: HomeCalendarFlowState
`@ObservedObject` var homeCalendarFlowState: HomeCalendarFlowState
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
at line 27, The property wrapper on homeCalendarFlowState is incorrect for an
externally injected object; replace the `@StateObject` declaration of
homeCalendarFlowState with `@ObservedObject` in CalendarView so the view does not
claim lifecycle ownership (homeCalendarFlowState is provided via
DIContainer/singleton). Update the CalendarView property declaration that
currently reads "@StateObject var homeCalendarFlowState: HomeCalendarFlowState"
to use "@ObservedObject" so the semantics match external injection.

Comment on lines +69 to +80
.onChange(of: homeCalendarFlowState.treatmentDate) { date in
if let date = date {
viewModel.updateDate(date: date)
homeCalendarFlowState.treatmentDate = nil
}
}
.onAppear {
if let date = homeCalendarFlowState.treatmentDate {
viewModel.updateDate(date: date)
homeCalendarFlowState.treatmentDate = nil
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

중복 로직을 헬퍼 함수로 추출하는 것을 고려해보세요.

onAppearonChange 모두 동일한 로직(updateDate 호출 후 treatmentDate = nil 설정)을 수행합니다. 중복을 줄이기 위해 헬퍼 함수로 추출할 수 있습니다.

♻️ 제안하는 리팩토링
+    private func handleTreatmentDate(_ date: Date?) {
+        guard let date = date else { return }
+        viewModel.updateDate(date: date)
+        homeCalendarFlowState.treatmentDate = nil
+    }
+
     // In body:
     .onChange(of: homeCalendarFlowState.treatmentDate) { date in
-        if let date = date {
-            viewModel.updateDate(date: date)
-            homeCalendarFlowState.treatmentDate = nil
-        }
+        handleTreatmentDate(date)
     }
     .onAppear {
-        if let date = homeCalendarFlowState.treatmentDate {
-            viewModel.updateDate(date: date)
-            homeCalendarFlowState.treatmentDate = nil
-        }
+        handleTreatmentDate(homeCalendarFlowState.treatmentDate)
     }
📝 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
.onChange(of: homeCalendarFlowState.treatmentDate) { date in
if let date = date {
viewModel.updateDate(date: date)
homeCalendarFlowState.treatmentDate = nil
}
}
.onAppear {
if let date = homeCalendarFlowState.treatmentDate {
viewModel.updateDate(date: date)
homeCalendarFlowState.treatmentDate = nil
}
}
private func handleTreatmentDate(_ date: Date?) {
guard let date = date else { return }
viewModel.updateDate(date: date)
homeCalendarFlowState.treatmentDate = nil
}
// ... (other code in body)
.onChange(of: homeCalendarFlowState.treatmentDate) { date in
handleTreatmentDate(date)
}
.onAppear {
handleTreatmentDate(homeCalendarFlowState.treatmentDate)
}
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
around lines 69 - 80, Extract the duplicated logic that checks
homeCalendarFlowState.treatmentDate, calls viewModel.updateDate(date:), and sets
homeCalendarFlowState.treatmentDate = nil into a single helper method (e.g.,
handleTreatmentDate()) inside CalendarView; then replace the bodies of both the
.onChange(of: homeCalendarFlowState.treatmentDate) closure and the .onAppear
closure to call that helper, ensuring you reference the same
viewModel.updateDate(date:) and mutate homeCalendarFlowState.treatmentDate from
within the helper so behavior remains identical.

Comment on lines +141 to +148
Task {
do {
try await fetchProcedureCountsOfMonth()
try await fetchTodayProcedureList()
} catch {
CherrishLogger.error(error)
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

비동기 작업의 취소 처리가 누락되었습니다.

updateDate가 빠르게 연속 호출되면 여러 Task가 동시에 실행되어 이전 요청의 결과가 최신 상태를 덮어쓸 수 있습니다. 또한, view가 사라질 때 Task가 취소되지 않습니다.

♻️ Task 관리 개선 제안

ViewModel에 Task 참조를 저장하고 새 호출 시 이전 Task를 취소하는 방식을 고려해보세요:

+    private var updateTask: Task<Void, Never>?
+
     func updateDate(date: Date) {
         selectedDate = date
         
         let calendar = Calendar.current
         let currentYear = calendar.component(.year, from: currentDate)
         let currentMonthVal = calendar.component(.month, from: currentDate)
         
         let targetYear = calendar.component(.year, from: date)
         let targetMonthVal = calendar.component(.month, from: date)
         
         let monthDiff = (targetYear - currentYear) * 12 + (targetMonthVal - currentMonthVal)
         currentMonth = monthDiff
         
-        Task {
+        updateTask?.cancel()
+        updateTask = Task {
             do {
                 try await fetchProcedureCountsOfMonth()
                 try await fetchTodayProcedureList()
             } catch {
                 CherrishLogger.error(error)
             }
         }
     }
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`
around lines 141 - 148, The code launches detached Tasks in CalendarViewModel
without cancellation, so rapid updateDate calls or view disappearance can spawn
overlapping tasks and stale results; add a stored Task? property (e.g.
fetchTask) on CalendarViewModel, cancel the existing fetchTask before creating a
new Task in updateDate, assign the new Task to fetchTask, and ensure you cancel
fetchTask in deinit or when the view disappears; also handle CancellationError
inside the Task (or check Task.isCancelled) so fetchProcedureCountsOfMonth and
fetchTodayProcedureList results are not applied after cancellation.

Comment on lines +81 to +85
try await createUserProcedureUseCase.execute(
scheduledDate: scheduledDate.toScheduledAtFormat,
recoveryDate: recoverDate.toRecoveryDateFormat,
treatments: selectedTreatments
)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

코드 포맷팅 개선 확인

함수 호출을 여러 줄로 분리하여 가독성을 향상시켰습니다. 파라미터 순서와 값은 변경되지 않았으며, 로직에 영향을 주지 않는 포맷팅 변경입니다.

다만, 이 변경사항은 PR의 주요 목적(캘린더 선택 날짜 네비게이션)과 직접적인 관련이 없는 것으로 보입니다. 포맷팅 개선은 좋지만, 별도의 커밋이나 PR로 분리하는 것이 변경 이력 관리에 더 명확할 수 있습니다.

🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/Treatment/TreatmentViewModel.swift`
around lines 81 - 85, 포맷팅 변경이 기능과 직접 관련되지 않으므로, TreatmentViewModel.swift의
createUserProcedureUseCase.execute(...) 호출부에서 여러 줄로 나눈 포맷팅 변경을 별도의 커밋(또는 PR)으로
분리하세요: 현재 변경된 createUserProcedureUseCase.execute 호출을 원래 기능 관련 커밋에서 제거하고 포맷팅 전/후
중 원하는 스타일을 반영한 작은 커밋을 새로 만들어 푸시한 뒤 이 PR은 캘린더 네비게이션 관련 변경만 포함하도록 정리하십시오.

Comment on lines +41 to +46
func selectUpcomingDate(_ dateString: String) {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
if let date = formatter.date(from: dateString) {
homeCalendarFlowState.treatmentDate = date
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Apple DateFormatter fixed format parsing locale calendar en_US_POSIX best practices

💡 Result:

Short best-practice checklist for fixed-format parsing with DateFormatter (Swift / Foundation)

  • Use a POSIX locale: formatter.locale = Locale(identifier: "en_US_POSIX") — this prevents user settings (24‑hour, language, etc.) from changing your format. (developer.apple.com)
  • Set an explicit calendar (usually Gregorian) when you need calendar-stable parsing: formatter.calendar = Calendar(identifier: .gregorian). Some locales use non‑Gregorian calendars which can change numeric interpretation. (stackoverflow.com)
  • Set an explicit timeZone for parsing (often UTC for internet timestamps): formatter.timeZone = TimeZone(secondsFromGMT: 0). (developer.apple.com)
  • Prefer specialized APIs when available: use ISO8601DateFormatter for ISO 8601 / RFC 3339 strings (less error-prone and faster). (sarunw.com)
  • For user-visible output, avoid fixed dateFormat; use dateStyle/timeStyle or localized templates (DateFormatter.dateFormatFromTemplate or setLocalizedDateFormatFromTemplate) so the UI respects user preferences. (developer.apple.com)
  • Cache formatters but handle locale changes: either create POSIX parsers on demand (they’re locale-invariant) or observe NSCurrentLocaleDidChangeNotification and rebuild cached formatters when user locale changes. (developer.apple.com)
  • When strict fixed-format parsing is critical, consider C functions (strptime_l/strftime_l) with the POSIX/C locale for deterministic behavior. (developer.apple.com)

Minimal example (Swift):

let f = DateFormatter()
f.locale = Locale(identifier: "en_US_POSIX")
f.calendar = Calendar(identifier: .gregorian)
f.timeZone = TimeZone(secondsFromGMT: 0)
f.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let date = f.date(from: isoString) // deterministic parsing

(Or use ISO8601DateFormatter for RFC 3339/ISO 8601 inputs.) (sarunw.com)

References (Apple):

Citations:


고정 포맷 파싱 시 Locale/Calendar 명시 필요.

현재 코드에서 명시적인 로케일과 캘린더가 없으므로 "yyyy-MM-dd" 파싱이 사용자의 기기 설정에 따라 실패할 수 있습니다. Apple의 공식 가이드에 따르면 고정 포맷 파싱에는 en_US_POSIX 로케일과 그레고리력을 명시해야 하며, 백엔드 기준 타임존이 있다면 timeZone도 지정해주세요.

🔧 제안 수정
-        let formatter = DateFormatter()
-        formatter.dateFormat = "yyyy-MM-dd"
+        let formatter = DateFormatter()
+        formatter.locale = Locale(identifier: "en_US_POSIX")
+        formatter.calendar = Calendar(identifier: .gregorian)
+        formatter.dateFormat = "yyyy-MM-dd"
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift`
around lines 41 - 46, The date parsing in selectUpcomingDate(_:) uses a fixed
format but doesn't set the DateFormatter.locale, calendar, or timeZone, which
can cause failures on some devices; update the DateFormatter in
selectUpcomingDate to set formatter.locale = Locale(identifier: "en_US_POSIX"),
formatter.calendar = Calendar(identifier: .gregorian) and (if backend expects a
specific zone) set formatter.timeZone appropriately before calling
formatter.date(from:), then assign the parsed Date to
homeCalendarFlowState.treatmentDate.

Copy link
Contributor

@sum130 sum130 left a comment

Choose a reason for hiding this comment

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

어푸푸~

@y-eonee y-eonee merged commit 6ee6977 into develop Jan 21, 2026
6 checks passed
@y-eonee y-eonee deleted the style/#139-캘린더-선택날짜 branch January 21, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Setting 프로젝트 세팅 시 사용 성용👶

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Style] 캘린더 선택날짜 색깔 변경

4 participants