Skip to content

[FEAT/#90] 시술 플로우 api 연결#112

Merged
usuuhyn merged 51 commits intodevelopfrom
feat/#90-procedure-api
Jan 20, 2026
Merged

[FEAT/#90] 시술 플로우 api 연결#112
usuuhyn merged 51 commits intodevelopfrom
feat/#90-procedure-api

Conversation

@usuuhyn
Copy link
Copy Markdown
Contributor

@usuuhyn usuuhyn commented Jan 20, 2026

Related issue 🛠

Work Description ✏️

  • 시술 플로우의 api를 연결했습니다.
    • 시술 목록 get
    • 시술 카테고리 목록 get
    • 시술 등록 post

Screenshot 📸

ProcedureList ProcedureCategory ProcedureRegister
video video video

Uncompleted Tasks 😅

  • empty view 까먹음 ㅎ ㅎ

To Reviewers 📢

Summary by CodeRabbit

  • 새로운 기능

    • 절차 검색/조회, 걱정 목록 조회, 사용자 절차 등록 기능(서비스·DTO·모델·저장소·데이터소스 포함)이 추가되었습니다.
    • 사용자 절차 생성/응답 모델과 요청 변환 로직이 포함되었습니다.
    • ISO 날짜 직렬화(LocalDate/LocalDateTime) 지원이 추가되었습니다.
    • 캘린더 새로고침 이벤트 버스가 도입되고 로컬 제공자로 주입됩니다.
    • 검색 결과 없음 빈 상태 UI가 추가되었습니다.
  • 리팩터

    • 캘린더·절차 내비게이션이 시작일(LocalDate)을 전달하도록 변경되었습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@usuuhyn usuuhyn self-assigned this Jan 20, 2026
@usuuhyn usuuhyn requested a review from a team as a code owner January 20, 2026 12:29
@usuuhyn usuuhyn added FEAT✨ 새로운 기능 구현 수현🍒 수현 담당 labels Jan 20, 2026
@usuuhyn usuuhyn linked an issue Jan 20, 2026 that may be closed by this pull request
3 tasks
@usuuhyn usuuhyn marked this pull request as draft January 20, 2026 12:29
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 20, 2026

Warning

Rate limit exceeded

@usuuhyn has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between fbad273 and 683fbc3.

📒 Files selected for processing (1)
  • app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureUiState.kt

Walkthrough

원격 시술/고민 API 연동을 위해 DTO·서비스·데이터소스·레포지토리 계층을 추가하고, 캘린더-시술 네비게이션에 startDate 전달 및 캘린더 새로고침 이벤트 버스를 도입해 UI·뷰모델 흐름을 수정했습니다. (직렬화 유틸리티 및 여러 UI/상태/DI 변경 포함)

Changes

Cohort / File(s) 변경 요약
직렬화 유틸리티
app/src/main/java/com/cherrish/android/core/util/DateSerializers.kt
LocalDate/LocalDateTime용 Kotlinx 직렬화기 추가
원격 DTO 응답
app/src/main/java/com/cherrish/android/data/remote/dto/response/ProceduresResponseDto.kt, .../WorryResponseDto.kt, .../UserProceduresResponseDto.kt
시술/고민/사용자 시술 응답 DTO 추가(날짜에 커스텀 직렬화 적용)
원격 DTO 요청
app/src/main/java/com/cherrish/android/data/remote/dto/request/UserProceduresRequestDto.kt
사용자 시술 등록 요청 DTO 추가 (scheduledAt, recoveryTargetDate 등)
Retrofit 서비스
app/src/main/java/com/cherrish/android/data/remote/service/ProcedureService.kt, .../WorryService.kt, .../UserProcedureService.kt
GET/POST 엔드포인트 인터페이스 추가
데이터소스 인터페이스
app/src/main/java/com/cherrish/android/data/remote/datasource/*.kt
ProcedureDataSource, WorryDataSource, UserProcedureDataSource 계약 추가
데이터소스 구현
app/src/main/java/com/cherrish/android/data/remote/datasourceimpl/*.kt
Retrofit 서비스에 위임하는 Impl(@Inject) 추가
도메인 모델 및 매핑
app/src/main/java/com/cherrish/android/data/model/*.kt
DTO↔도메인 모델 추가 및 변환 확장함수(카테고리 폴백 로직 포함)
레포지토리(인터페이스·구현)
app/src/main/java/com/cherrish/android/data/repository/*.kt, .../repositoryimpl/*.kt
Procedure/Worry/UserProcedure 레포지토리 인터페이스 및 Impl 추가(데이터소스 호출을 suspendRunCatching으로 래핑)
DI 모듈
app/src/main/java/com/cherrish/android/data/di/*.kt, app/src/main/java/com/cherrish/android/core/local/di/AppModule.kt
데이터소스/레포지토리/서비스 바인딩 추가 및 CalendarRefreshEventBus 제공자 등록
캘린더 이벤트 버스
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarEventBus.kt
CalendarRefreshEventBus, CalendarEvent, LocalCalendarEventBus 추가(SharedFlow 기반)
캘린더 네비/뷰모델/화면
app/src/main/java/com/cherrish/android/presentation/calendar/*.kt, .../navigation/CalendarNavigator.kt
NavigateToProcedure에 startDate 전달(타입 변경), ViewModel에 이벤트버스 구독·refresh 로직 추가, 네비게이션 시그니처/경로 변경
시술 화면/상태/뷰모델
app/src/main/java/com/cherrish/android/presentation/calendar/procedure/*.kt
저장소 주입, fetchWorries/fetchProcedures/onSearchAction/onComplete 로직 및 completeEvent 추가, startDay 기반 계산으로 상태 변경
시술 관련 컴포넌트/UI
app/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/SelectedProcedureBottomSheet.kt, .../content/FilteringWithSearchContent.kt, .../content/CategoryContent.kt
선택 증가 시 스크롤 애니메이션, 검색 결과 빈 상태 UI 추가, 불필요 import 제거
앱 상태·초기화
app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt, MainScreen.kt, MainActivity.kt
CalendarRefreshEventBus 주입 및 CompositionLocal 제공, navigateToProcedure 시그니처(startDate) 변경

Sequence Diagram(s)

sequenceDiagram
    participant UI as 시술 화면
    participant VM as ProcedureViewModel
    participant REPO as ProcedureRepository
    participant DS as ProcedureDataSource
    participant SVC as ProcedureService
    participant API as 원격 API

    UI->>VM: onSearchAction(query)
    VM->>REPO: fetchProcedures(keyword, worryId)
    REPO->>DS: getProcedures(keyword, worryId)
    DS->>SVC: getProcedures(keyword, worryId)
    SVC->>API: GET /api/procedures?keyword=...&worryId=...
    API-->>SVC: BaseResponse<ProceduresResponseDto>
    SVC-->>DS: BaseResponse<ProceduresResponseDto>
    DS-->>REPO: BaseResponse<ProceduresResponseDto>
    REPO->>REPO: toModel()
    REPO-->>VM: Result<ProceduresResponseModel>
    VM-->>UI: UI 상태 업데이트 (시술 목록)
Loading
sequenceDiagram
    participant UI as 시술 화면
    participant VM as ProcedureViewModel
    participant REPO as UserProcedureRepository
    participant DS as UserProcedureDataSource
    participant SVC as UserProcedureService
    participant API as 원격 API
    participant BUS as CalendarRefreshEventBus

    UI->>VM: onComplete()
    VM->>VM: UserProceduresRequestModel 생성
    VM->>REPO: addUserProcedures(request)
    REPO->>DS: addUserProcedures(request.toDto())
    DS->>SVC: addUserProcedures(requestDto)
    SVC->>API: POST /api/user-procedures
    API-->>SVC: BaseResponse<UserProceduresResponseDto>
    SVC-->>DS: BaseResponse<UserProceduresResponseDto>
    DS-->>REPO: BaseResponse<UserProceduresResponseDto>
    REPO->>REPO: toModel()
    REPO-->>VM: Result<UserProceduresResponseModel>
    VM->>BUS: emit(RefreshRequired)
    VM-->>UI: completeEvent (네비게이션)
Loading
sequenceDiagram
    participant UI as 시술 화면
    participant VM as ProcedureViewModel
    participant REPO as WorryRepository
    participant DS as WorryDataSource
    participant SVC as WorryService
    participant API as 원격 API

    UI->>VM: 초기화
    VM->>VM: fetchWorries()
    VM->>REPO: getWorries()
    REPO->>DS: getWorries()
    DS->>SVC: getWorries()
    SVC->>API: GET /api/worries
    API-->>SVC: BaseResponse<List<WorryResponseDto>>
    SVC-->>DS: BaseResponse<List<WorryResponseDto>>
    DS-->>REPO: BaseResponse<List<WorryResponseDto>>
    REPO->>REPO: toModel()
    REPO-->>VM: Result<List<WorryModel>>
    VM-->>UI: 고민 목록 표시
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • nhyeonii
  • sohee6989
  • hyeminililo
🚥 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 제목은 주요 변경사항을 명확하게 설명하고 있으며, 시술 플로우 API 연결이라는 핵심 내용을 잘 반영하고 있습니다.
Description check ✅ Passed PR 설명은 템플릿을 따르고 있으며, 관련 이슈, 작업 내용, 스크린샷, 완료되지 않은 작업을 모두 포함하고 있습니다.
Linked Issues check ✅ Passed 코드 변경사항이 링크된 이슈 #90의 모든 요구사항을 충족합니다: 시술 목록 조회, 카테고리 목록 조회, 시술 등록 API가 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 시술 플로우 API 연결이라는 범위 내에 있으며, 불필요한 변경사항이나 범위를 벗어난 코드는 발견되지 않았습니다.

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


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

@nhyeonii nhyeonii left a comment

Choose a reason for hiding this comment

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

우ㅇ옹 ~~ 넘 잘햇어요 리뷰 하번만 확인해죠요 !!!

Comment on lines +23 to +26
id = id,
name = name,
category = category?.takeIf { it.isNotBlank() }
?: worries.map { it.content }.filter { it.isNotBlank() }.joinToString(" | "),
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.

Suggested change
id = id,
name = name,
category = category?.takeIf { it.isNotBlank() }
?: worries.map { it.content }.filter { it.isNotBlank() }.joinToString(" | "),
id = this.id,
name = this.name,
category = this.category?.takeIf { it.isNotBlank() }
?: this.worries.map { it.content }.filter { it.isNotBlank() }.joinToString(" | "),

P2: 요런식으로 작성해주는 거 어때요 ?! mapper / 변환 코드에서는 this를 붙이면 프로퍼티 출처가 명확해져서 가독성과 안전성이 좋아지는 것 같아용! 특히 id, name처럼 이름이 겹치기 쉬운 경우에 실수 방지도 댄답니닿ㅎㅎ

Comment on lines +27 to +28
minDowntimeDays = minDowntimeDays,
maxDowntimeDays = maxDowntimeDays
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.

Suggested change
minDowntimeDays = minDowntimeDays,
maxDowntimeDays = maxDowntimeDays
minDowntimeDays = this.minDowntimeDays,
maxDowntimeDays = this.maxDowntimeDays

val downtimeDays: Int
)

fun UserProceduresRequestModel.toDto(): UserProceduresRequestDto =
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.

P1 여기두

val recoveryTargetDate: LocalDate
)

fun UserProceduresResponseDto.toModel(): UserProceduresResponseModel =
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.

여기둥

procedures = procedures.map { it.toModel() }
)

fun CreatedUserProcedureDto.toModel(): CreatedUserProcedureModel =
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 thread app/src/main/java/com/cherrish/android/presentation/calendar/CalendarScreen.kt Outdated
Comment on lines +257 to +268
fun refreshCalendar() {
val currentState = (_uiState.value as? UiState.Success)?.data
val selectedYearMonth = currentState?.selectedYearMonth ?: YearMonth.now()
val selectedDate = currentState?.selectedDate ?: LocalDate.now()

monthlyCache.clear()
dailyCache.clear()
loadMonthlyCalendar(
yearMonth = selectedYearMonth,
selectedDate = selectedDate
)
}
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.

Suggested change
fun refreshCalendar() {
val currentState = (_uiState.value as? UiState.Success)?.data
val selectedYearMonth = currentState?.selectedYearMonth ?: YearMonth.now()
val selectedDate = currentState?.selectedDate ?: LocalDate.now()
monthlyCache.clear()
dailyCache.clear()
loadMonthlyCalendar(
yearMonth = selectedYearMonth,
selectedDate = selectedDate
)
}
fun refreshCalendar() {
_uiState.updateSuccess { currentState ->
monthlyCache.clear()
dailyCache.clear()
currentState.selectedDate?.let {
loadMonthlyCalendar(
yearMonth = currentState.selectedYearMonth,
selectedDate = it
)
}
currentState
}
}

P1 : 확장함수 사용해서 이렇게 작성하는건 어때요 ?!

}

fun onAddButtonClick() {
val selectedDate = (_uiState.value as? UiState.Success)?.data?.selectedDate
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.

P2 : onAddButtonClick이 함수

Suggested change
val selectedDate = (_uiState.value as? UiState.Success)?.data?.selectedDate
fun onAddButtonClick() {
viewModelScope.launch {
val selectedDate = when (val state = _uiState.value) {
is UiState.Success -> state.data.selectedDate
else -> LocalDate.now()
}
selectedDate?.let { _sideEffect.emit(CalendarSideEffect.NavigateToProcedure(it)) }
}
}

이렇게 when으로 처리하면 가독성이 좋구 타입 캐스팅 명확해질 것 같은데 어때요잉 !?

)
}
}
.onFailure { e ->
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.

Suggested change
.onFailure { e ->
.onLongFailure { e ->

사용해쥬시용ㅎㅎㅎ

_uiState.updateSuccess { ProcedureUiState.FakeNormal }
_completeEvent.tryEmit(Unit)
}
.onFailure { e ->
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.

Suggested change
.onFailure { e ->
.onLogFailure {

Copy link
Copy Markdown
Contributor

@sohee6989 sohee6989 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~!!
너무 복잡한 이걸 어떻게 구현하셨는지........

procedures = procedures.map { it.toModel() }
)

fun CreatedUserProcedureDto.toModel(): CreatedUserProcedureModel =
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.

p3: (단순 궁금) 위에서는
procedures = procedures.map { UserProcedureItemDto(it.procedureId, it.downtimeDays) } 처럼 한 번에 매핑하셨는데
여기서는 Model을 2개로 나눠 각각 매퍼로 변환하신 이유가 있을까요?

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.

응답이 계층 구조라 상위/하위 모델로 나눠 매핑햇습니다!

procedures = procedures.map { UserProcedureItemDto(it.procedureId, it.downtimeDays) }
)

data class UserProceduresResponseModel(
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.

p2: request/response 모델은 역할(입력/출력)과 변경 이유가 달라서
한 파일에 같이 두기보다 분리해두면 더 깔끔할 것 같아요ㅎㅎ

color = CherrishTheme.colors.gray600
)

Spacer(modifier = Modifier.height(126.dp))
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.

p2: 이거 의미 없지 않나여?!
지금 화면 정렬을 상위에서 고정값으로 해주고있으니까요~!!

item {
EmptySearchResult(
modifier = Modifier
.fillParentMaxSize()
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.

p2: 이거 한줄로 써줘도 될듯?!합니다ㅎㅎ


private fun ProcedureModel.toUiModel(): ProcedureCardItemUiModel =
ProcedureCardItemUiModel(
id = id,
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.

p2: 여기도 위에 나현이 코리처러 this 써주면 좋을듯합니다!

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.

최고

fun CalendarRoute(
paddingValues: PaddingValues,
onNavigateToProcedure: () -> Unit,
onNavigateToProcedure: (LocalDate) -> Unit,
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.

p2: 수현씨 이거 네비이동 변수명 navigateTo~~로 통일해요오~!!


@Serializable
private data object Procedure : Route
data class Procedure(val startDate: String)
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.

p1: 이거 LocalDate로 해야되는거 아닌가여?!
다른 곳에서도 타입을 LocalDate로 쓰고 있어서요!

Copy link
Copy Markdown
Contributor Author

@usuuhyn usuuhyn Jan 20, 2026

Choose a reason for hiding this comment

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

이거는 나중에 이슈 파서 해결하겟습니다

private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private val startDateArg = runCatching {
LocalDate.parse(savedStateHandle.toRoute<Procedure>().startDate)
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.

p1: CalendarNavigator에서 코리 반영해주시면 이 부분도 따로 파싱이 필요 없지 않나여?!

Copy link
Copy Markdown
Contributor Author

@usuuhyn usuuhyn Jan 20, 2026

Choose a reason for hiding this comment

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

이것도 !! 나중에 이슈 파서 따로 해결할게요

UiState.Success(
ProcedureUiState.FakeNormal.copy(
procedureItems = ProcedureUiState.FakeProcedureCardItems.procedureItems
procedureItems = persistentListOf(),
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.

p1: 여기 fake 데이터 넣지말고 로딩으로 처리하는거 어떠세요??

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt`:
- Around line 287-298: refreshCalendar currently clears monthlyCache/dailyCache
but only calls loadMonthlyCalendar when currentState.selectedDate is non-null,
leaving the UI stale if selectedDate is null; change the logic in
refreshCalendar (inside _uiState.updateSuccess) to compute a fallback date
(e.g., first day of currentState.selectedYearMonth or LocalDate.now() adjusted
to that month) when currentState.selectedDate is null and then call
loadMonthlyCalendar with yearMonth = currentState.selectedYearMonth and
selectedDate = fallbackDate so the UI always reloads; keep the cache clears
as-is and ensure you update only within the same updateSuccess block referencing
refreshCalendar, selectedDate, selectedYearMonth, and loadMonthlyCalendar.
- Around line 277-284: onAddButtonClick에서 _uiState 확인 시 UiState.Success일 때
state.data.selectedDate가 null이면 네비게이션이 스킵되므로, UiState.Success 케이스에서도
selectedDate가 null이면 LocalDate.now() 같은 기본값을 사용하도록 처리하세요; 구체적으로
onAddButtonClick의 when 분기(UiState.Success)에서 selectedDate를 안전하게 널 병합하거나 기본값으로
대체한 뒤 _sideEffect.emit(CalendarSideEffect.NavigateToProcedure(...))를 항상 호출하도록
변경하세요.

Comment on lines 277 to +284
fun onAddButtonClick() {
viewModelScope.launch {
_sideEffect.emit(CalendarSideEffect.NavigateToProcedure)
val selectedDate = when (val state = _uiState.value) {
is UiState.Success -> state.data.selectedDate
else -> LocalDate.now()
}
selectedDate?.let { _sideEffect.emit(CalendarSideEffect.NavigateToProcedure(it)) }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Success 상태에서 selectedDate가 null이면 네비게이션이 누락됩니다.
Success지만 selectedDate가 null인 경우 emit이 스킵되어 버튼이 무반응이 될 수 있습니다. null인 경우에도 기본 날짜로 이동하도록 보완하는 게 안전합니다.

✅ 제안 수정
-        viewModelScope.launch {
-            val selectedDate = when (val state = _uiState.value) {
-                is UiState.Success -> state.data.selectedDate
-                else -> LocalDate.now()
-            }
-            selectedDate?.let { _sideEffect.emit(CalendarSideEffect.NavigateToProcedure(it)) }
-        }
+        viewModelScope.launch {
+            val selectedDate = when (val state = _uiState.value) {
+                is UiState.Success -> state.data.selectedDate ?: LocalDate.now()
+                else -> LocalDate.now()
+            }
+            _sideEffect.emit(CalendarSideEffect.NavigateToProcedure(selectedDate))
+        }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt`
around lines 277 - 284, onAddButtonClick에서 _uiState 확인 시 UiState.Success일 때
state.data.selectedDate가 null이면 네비게이션이 스킵되므로, UiState.Success 케이스에서도
selectedDate가 null이면 LocalDate.now() 같은 기본값을 사용하도록 처리하세요; 구체적으로
onAddButtonClick의 when 분기(UiState.Success)에서 selectedDate를 안전하게 널 병합하거나 기본값으로
대체한 뒤 _sideEffect.emit(CalendarSideEffect.NavigateToProcedure(...))를 항상 호출하도록
변경하세요.

Comment on lines +287 to 298
fun refreshCalendar() {
_uiState.updateSuccess { currentState ->
monthlyCache.clear()
dailyCache.clear()
currentState.selectedDate?.let {
loadMonthlyCalendar(
yearMonth = currentState.selectedYearMonth,
selectedDate = it
)
}
currentState
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

selectedDate가 null이면 캐시만 비우고 재로딩이 되지 않습니다.
리프레시 이벤트 시 선택 날짜가 null이면 loadMonthlyCalendar가 호출되지 않아 UI가 최신화되지 않을 수 있습니다. 선택 날짜가 없을 때는 현재 월의 첫째 날 등으로 보완해 주세요.

✅ 제안 수정
     fun refreshCalendar() {
         _uiState.updateSuccess { currentState ->
             monthlyCache.clear()
             dailyCache.clear()
-            currentState.selectedDate?.let {
-                loadMonthlyCalendar(
-                    yearMonth = currentState.selectedYearMonth,
-                    selectedDate = it
-                )
-            }
+            val yearMonth = currentState.selectedYearMonth
+            val selectedDate = currentState.selectedDate ?: yearMonth.atDay(1)
+            loadMonthlyCalendar(
+                yearMonth = yearMonth,
+                selectedDate = selectedDate
+            )
             currentState
         }
     }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt`
around lines 287 - 298, refreshCalendar currently clears monthlyCache/dailyCache
but only calls loadMonthlyCalendar when currentState.selectedDate is non-null,
leaving the UI stale if selectedDate is null; change the logic in
refreshCalendar (inside _uiState.updateSuccess) to compute a fallback date
(e.g., first day of currentState.selectedYearMonth or LocalDate.now() adjusted
to that month) when currentState.selectedDate is null and then call
loadMonthlyCalendar with yearMonth = currentState.selectedYearMonth and
selectedDate = fallbackDate so the UI always reloads; keep the cache clears
as-is and ensure you update only within the same updateSuccess block referencing
refreshCalendar, selectedDate, selectedYearMonth, and loadMonthlyCalendar.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/navigation/CalendarNavigator.kt`:
- Around line 19-25: Procedure uses `@Contextual` on startDate but no
serializersModule registering a LocalDate serializer exists, causing
MissingSerializerException when navigateToProcedure is called; fix by either
adding a serializersModule to your Json in NetworkModule that registers
LocalDateSerializer (registerLocalDate/Contextual(LocalDate::class,
LocalDateSerializer)) so `@Contextual` works, or change Procedure.startDate to use
explicit serialization via `@Serializable`(with = LocalDateSerializer::class) to
match other DTOs (update Procedure declaration and imports accordingly).

In
`@app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt`:
- Around line 37-45: ProcedureViewModel의 startDateArg 초기화에서
savedStateHandle.get<String>("startDate")가 null이거나 포맷이 맞지 않으면 LocalDate.parse에서
NPE/예외가 발생하므로, savedStateHandle에서 값을 안전하게 읽어와(예:
savedStateHandle.get<String>("startDate")?.let { ... }) 명시적
포맷(DateTimeFormatter.ISO_LOCAL_DATE)으로 파싱하고 실패 시 기본값을 사용하거나(예: LocalDate.now())
명확한 IllegalArgumentException을 던지도록 변경해 startDateArg 초기화를 안전하게 만드세요; 대상 식별자:
ProcedureViewModel, startDateArg, savedStateHandle, LocalDate.parse.
♻️ Duplicate comments (2)
app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt (2)

346-379: 등록 실패 시 사용자 피드백이 없습니다.

onLogFailure 블록이 비어 있어 실패 시 UI가 조용히 실패합니다. 에러 상태 전파 또는 실패 이벤트 발행이 필요합니다.


502-508: 광범위 예외 삼킴으로 디버깅이 어려워집니다.

의도된 예외만 처리하도록 범위를 좁히는 편이 안전합니다.

🛠️ 개선 예시
 private fun ProcedureUiState.recoveryTargetDateOrNull(): LocalDate? {
     return try {
         if (year.isBlank() || month.isBlank() || day.isBlank()) return null
         LocalDate.of(year.toInt(), month.toInt(), day.toInt())
-    } catch (e: Exception) {
+    } catch (e: java.time.DateTimeException) {
+        null
+    } catch (e: NumberFormatException) {
         null
     }
 }

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt (1)

225-233: 새로 선택한 카드가 항상 맨 앞으로 이동합니다 — UX 의도 확인 필요.

persistentListOf(cardId) + currentList로 인해 선택 순서가 변경되며, 이는 DowntimeContent의 카드 표시 순서와 최종 제출 순서에도 영향을 미칩니다. 의도된 UX가 아니라면 선택 순서를 유지하거나 기존 리스트의 맨 앞이 아닌 다른 위치에 추가하도록 조정해 주세요.

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt`:
- Around line 76-107: The onLogFailure block in fetchWorries() is empty which
leaves _uiState stuck in UiState.Loading on failure; update the onLogFailure
handler to set a fallback UI state (e.g., _uiState.value =
UiState.Success(ProcedureUiState(worries = ProcedureUiState.FakeNormal.worries,
startDay = startDateArg)) or set an error state like UiState.Error) and log the
exception; modify the onLogFailure in fetchWorries() to reference _uiState,
UiState.Success/UiState.Error, ProcedureUiState.FakeNormal.worries, and the
caught exception so the UI always progresses on repository failure.
- Around line 47-49: The current startDateArg initializer swallows parse errors
and silently falls back to LocalDate.now(); update it to surface failures by
invoking the existing logging helper and preserve a safe fallback: wrap the
parse in runCatching, call onLogFailure on the caught exception with a clear
message referencing the route value (use
savedStateHandle.toRoute<Procedure>().startDate to include the raw input), and
then return the fallback LocalDate.now() only after logging; change the
expression around startDateArg so the failure is logged (e.g., runCatching {
LocalDate.parse(...) }.onFailure { it.onLogFailure("Failed to parse
Procedure.startDate: ...") }.getOrElse { LocalDate.now() }).
♻️ Duplicate comments (2)
app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt (2)

354-385: 시술 등록 실패 처리 부재(중복 이슈).

이 구간의 실패 처리 부재는 이전 리뷰에서 이미 지적된 동일 이슈입니다.


507-513: 예외 삼키기 범위가 너무 넓습니다(중복 이슈).

Exception 전체를 잡아 무시하고 있어 디버깅이 어렵습니다. 이전 리뷰에서 제안한 대로 DateTimeException/NumberFormatException 등 구체 예외로 좁혀 주세요.

Comment on lines +47 to +49
private val startDateArg = runCatching {
LocalDate.parse(savedStateHandle.toRoute<Procedure>().startDate)
}.getOrElse { LocalDate.now() }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt | head -80

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 4010


🏁 Script executed:

wc -l app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 170


🏁 Script executed:

# Search for error handling and logging patterns in the ViewModel
rg "getOrElse|getOrNull|runCatching|Timber|Log" app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt -A 2

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 689


🏁 Script executed:

# Check if there's an error state or failure handling in UiState
rg "UiState|Error|Failure" app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt -B 2 -A 2

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 2539


startDate 파싱 실패 시 무음 처리는 잘못된 일정으로 이어질 수 있습니다.

현재 예외를 삼키고 LocalDate.now()로 대체되어, 유효하지 않은 route 값이 조용히 "오늘"로 반영됩니다. 코드베이스의 다른 부분에서는 onLogFailure 확장 함수를 사용하여 실패를 기록하고 있는데, 이 부분에서만 무음 처리되고 있습니다. 최소한 로그를 통해 파싱 실패를 표면화하거나, 명시적으로 에러를 처리하는 것이 좋습니다.

🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt`
around lines 47 - 49, The current startDateArg initializer swallows parse errors
and silently falls back to LocalDate.now(); update it to surface failures by
invoking the existing logging helper and preserve a safe fallback: wrap the
parse in runCatching, call onLogFailure on the caught exception with a clear
message referencing the route value (use
savedStateHandle.toRoute<Procedure>().startDate to include the raw input), and
then return the fallback LocalDate.now() only after logging; change the
expression around startDateArg so the failure is logged (e.g., runCatching {
LocalDate.parse(...) }.onFailure { it.onLogFailure("Failed to parse
Procedure.startDate: ...") }.getOrElse { LocalDate.now() }).

Comment on lines +76 to +107
fun fetchWorries() {
viewModelScope.launch {
worryRepository.getWorries()
.onSuccess { worries ->

val mapped = worries
.map { ProcedureWorryUiModel(id = it.id, content = it.content) }
.toPersistentList()

val worriesToUse = if (mapped.isEmpty()) {
ProcedureUiState.FakeNormal.worries
} else {
mapped
}

val currentState = _uiState.value
if (currentState is UiState.Success) {
_uiState.value = currentState.copy(
data = currentState.data.copy(worries = worriesToUse)
)
} else {
_uiState.value = UiState.Success(
ProcedureUiState(
worries = worriesToUse,
startDay = startDateArg
)
)
}
}
.onLogFailure { e ->
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

worries 로딩 실패 시 UI가 Loading에 고정될 수 있습니다.

onLogFailure가 비어 있어 초기 UiState.Loading에서 벗어나지 못할 수 있습니다. 실패 시에도 UI가 진행되도록 최소한 fallback/에러 상태로 전환해 주세요.

🛠️ 제안 변경
             worryRepository.getWorries()
                 .onSuccess { worries ->
                     // ...
                 }
-                .onLogFailure { e ->
-                }
+                .onLogFailure { e ->
+                    // 실패 시에도 로딩 해제 (프로젝트의 Error 상태가 있다면 그쪽 사용 권장)
+                    _uiState.value = UiState.Success(
+                        ProcedureUiState(
+                            worries = ProcedureUiState.FakeNormal.worries,
+                            startDay = startDateArg
+                        )
+                    )
+                    // TODO: 사용자 피드백(스낵바/이벤트 등)
+                }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt`
around lines 76 - 107, The onLogFailure block in fetchWorries() is empty which
leaves _uiState stuck in UiState.Loading on failure; update the onLogFailure
handler to set a fallback UI state (e.g., _uiState.value =
UiState.Success(ProcedureUiState(worries = ProcedureUiState.FakeNormal.worries,
startDay = startDateArg)) or set an error state like UiState.Error) and log the
exception; modify the onLogFailure in fetchWorries() to reference _uiState,
UiState.Success/UiState.Error, ProcedureUiState.FakeNormal.worries, and the
caught exception so the UI always progresses on repository failure.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt`:
- Around line 390-410: fetchProcedures currently leaves the onLogFailure block
empty so API failures give no user feedback; implement error handling in
onLogFailure { error -> ... } to update the view model state (use
_uiState.update { current -> current.copy(...) }) to set an error message/flag
(e.g., errorMessage or showError/snackbar state) and reset any loading
indicators and selection fields as appropriate, and ensure the error message
uses error.message (or a localized fallback) so the UI can display whether it
was a network error vs. no results; reference the fetchProcedures function and
the _uiState property to locate and apply the fix.
♻️ Duplicate comments (4)
app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt (4)

47-49: startDate 파싱 실패 시 로깅이 누락되었습니다.

runCatching으로 안전하게 처리되지만, 실패 시 무음으로 LocalDate.now()로 대체됩니다. 잘못된 route 값이 전달될 경우 디버깅이 어려워질 수 있습니다.

♻️ 로깅 추가 제안
     private val startDateArg = runCatching {
         LocalDate.parse(savedStateHandle.toRoute<Procedure>().startDate)
-    }.getOrElse { LocalDate.now() }
+    }.onFailure { e ->
+        Timber.w(e, "Failed to parse startDate, falling back to today")
+    }.getOrElse { LocalDate.now() }

104-106: API 실패 시 UI가 Loading 상태에 고정됩니다.

onLogFailure 블록이 비어 있어 worryRepository.getWorries() 실패 시 UI가 UiState.Loading에서 벗어나지 못합니다.

🛠️ 실패 시 fallback 상태 설정
             .onLogFailure { e ->
+                _uiState.value = UiState.Success(
+                    ProcedureUiState(
+                        worries = ProcedureUiState.FakeNormal.worries,
+                        startDay = startDateArg
+                    )
+                )
             }

377-384: 시술 등록 실패 시 사용자 피드백이 없습니다.

onLogFailure 블록이 비어 있어 API 호출 실패 시 사용자에게 어떤 피드백도 제공되지 않습니다.

🛠️ 에러 처리 추가 제안
             .onLogFailure { e ->
+                // 에러 이벤트 발행 또는 스낵바 표시
+                Timber.e(e, "시술 등록 실패")
             }

505-512: SwallowedException: 예외가 무시되고 있습니다.

Exception을 catch하고 로깅 없이 null을 반환합니다. 예상치 못한 예외가 발생할 경우 디버깅이 어려워집니다. Based on static analysis hint.

🛠️ 특정 예외만 처리하도록 개선
 private fun ProcedureUiState.recoveryTargetDateOrNull(): LocalDate? {
     return try {
         if (year.isBlank() || month.isBlank() || day.isBlank()) return null
         LocalDate.of(year.toInt(), month.toInt(), day.toInt())
-    } catch (e: Exception) {
+    } catch (e: java.time.DateTimeException) {
+        null
+    } catch (e: NumberFormatException) {
         null
     }
 }

Comment on lines +390 to 410
private fun fetchProcedures(keyword: String?, worryId: Long?) {
viewModelScope.launch {
procedureRepository.getProcedures(keyword = keyword, worryId = worryId)
.onSuccess { response ->
val items = response.procedures
.map { it.toUiModel() }
.toPersistentList()

ProcedureUiState.FakeNormal
_uiState.updateSuccess { current ->
current.copy(
procedureItems = items,
selectedProcedureCardIds = persistentListOf(),
procedureDowntimeMap = emptyMap(),
selectedProcedureForDowntime = null,
showDowntimeBottomSheet = false
)
}
}
.onLogFailure { }
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

시술 목록 조회 실패 시 에러 처리가 누락되었습니다.

fetchProceduresonLogFailure 블록이 비어 있어 API 실패 시 사용자에게 피드백이 제공되지 않습니다. 검색 결과가 없는 것인지 네트워크 오류인지 구분할 수 없습니다.

🛠️ 에러 처리 추가 제안
                 .onLogFailure { e ->
+                    Timber.e(e, "시술 목록 조회 실패")
+                    // TODO: 사용자에게 에러 상태 표시 (스낵바 또는 에러 UI)
                 }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/procedure/ProcedureViewModel.kt`
around lines 390 - 410, fetchProcedures currently leaves the onLogFailure block
empty so API failures give no user feedback; implement error handling in
onLogFailure { error -> ... } to update the view model state (use
_uiState.update { current -> current.copy(...) }) to set an error message/flag
(e.g., errorMessage or showError/snackbar state) and reset any loading
indicators and selection fields as appropriate, and ensure the error message
uses error.message (or a localized fallback) so the UI can display whether it
was a network error vs. no results; reference the fetchProcedures function and
the _uiState property to locate and apply the fix.

Copy link
Copy Markdown
Contributor

@nhyeonii nhyeonii left a comment

Choose a reason for hiding this comment

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

유수현바보띠

@usuuhyn usuuhyn merged commit affc12e into develop Jan 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FEAT✨ 새로운 기능 구현 수현🍒 수현 담당

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 시술 플로우 api 연결

3 participants