Skip to content

[NDGL-68] 따라가기 여행 템플릿 관련 API 연동#23

Merged
mj010504 merged 6 commits into
developfrom
feature/NDGL-68
Feb 16, 2026
Merged

[NDGL-68] 따라가기 여행 템플릿 관련 API 연동#23
mj010504 merged 6 commits into
developfrom
feature/NDGL-68

Conversation

@mj010504
Copy link
Copy Markdown
Contributor

@mj010504 mj010504 commented Feb 16, 2026

개요

  • 따라가기 여행 템플릿 관련 API 연동

변경사항

  • 여행 템플릿 일정 조회 API 연동
  • 여행 템플릿 영상 정보 조회 API 연동
  • feature:travel 모듈 공통으로 사용되는 model 정리
  • DatePicker, PlaceDetail 화면에 Scaffold 적용

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 여행 템플릿 일정(Itinerary)과 콘텐츠 정보 조회 기능 추가
    • 장소 상세 정보에 가격대, 사진, 리뷰, 팁 정보 표시 기능 추가
    • 영업시간, 교통 수단 정보 표시 개선
  • 개선사항

    • 여행 템플릿 네비게이션 개선 (일자 정보 추가 전달)
    • 장소 상세 화면 레이아웃 및 UI 개선

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 16, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

이 PR은 여행 템플릿 데이터 로딩 기능을 추가하고, 데이터 모델들을 공유 모델 패키지로 이동하며, UI 레이아웃을 Scaffold 기반으로 리팩토링하고, 네비게이션에 days 파라미터를 추가합니다.

Changes

Cohort / File(s) Summary
API 및 저장소 계층
data/travel/src/main/java/com/yapp/ndgl/data/travel/api/TravelTemplateApi.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/TravelTemplateRepository.kt
여행 템플릿 여정 및 콘텐츠 정보 조회를 위한 두 개의 새로운 API 엔드포인트와 저장소 메서드 추가.
데이터 모델 (데이터 계층)
data/travel/src/main/java/com/yapp/ndgl/data/travel/model/TransportCategory.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/model/TravelTemplateContentInfo.kt, data/travel/src/main/java/com/yapp/ndgl/data/travel/model/TravelTemplateItinerary.kt
여행 템플릿 응답을 위한 새로운 직렬화 가능한 데이터 모델 및 중첩 구조 정의.
유틸리티
core/util/src/main/java/com/yapp/ndgl/core/util/CountryCodeUtil.kt
두 자리 국가 코드를 표시 이름으로 변환하는 확장 함수 추가.
리소스
core/ui/src/main/res/values/strings.xml, .gitignore
새로운 문자열 리소스 및 gitignore 항목 추가.
Feature 모델 패키지
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/OpeningHours.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/PlaceDetailTab.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/PlacePhoto.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/PlaceType.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/PriceRange.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/TransportSegment.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/TransportType.kt
여행 기능의 공유 모델 타입들을 새로운 모델 패키지로 이동/추가 (개장시간 파싱, 운송 정보, 가격대, 장소 타입 정의).
FollowTravel 계약 및 상태 관리
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/followtravel/FollowTravelContract.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/followtravel/FollowTravelViewModel.kt
상태 구조 리팩토링 (travelId, days, countryCode 필드 추가), 테스트 데이터를 실제 API 호출로 변경, 데이터 변환 로직 추가.
FollowTravel UI 계층
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/followtravel/FollowTravelScreen.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/followtravel/component/ContentCard.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/followtravel/component/PlaceItem.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/followtravel/component/TransportSegment.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/followtravel/component/TravelMap.kt
상태 구조 변경에 맞춰 UI 업데이트 및 모델 패키지에서 타입 임포트 변경.
DatePicker 레이아웃 리팩토링
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/datepicker/DatePickerScreen.kt
Scaffold 기반 레이아웃으로 변경, innerPadding 파라미터 제거, statusBarsPadding 추가.
PlaceDetail 계약
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/placedetail/PlaceDetailContract.kt
PlaceInfo 필드 구조 변경 (priceRange 추가, tip 정보를 TipContent로 리팩토링), 공유 모델 패키지에서 타입 임포트, 일부 타입 제거.
PlaceDetail 화면 및 컴포넌트
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/placedetail/PlaceDetailScreen.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/placedetail/PlaceDetailViewModel.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/placedetail/component/...
Scaffold 레이아웃 적용, 데이터 모델 변경 반영, PlaceDetailTabRow 컨테이너 높이 조정, PlaceInfoRow 새로운 컴포넌트 추가, PlaceInfoTab 리팩토링.
TravelDetail 타입 마이그레이션
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/traveldetail/TravelDetailContract.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/traveldetail/TravelDetailScreen.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/traveldetail/TravelDetailViewModel.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/traveldetail/component/...
PlaceType, TransportSegment, TransportType을 공유 모델 패키지로 이동, 관련 임포트 업데이트.
Travel 화면 및 네비게이션
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/travel/TravelContract.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/travel/TravelScreen.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/travel/TravelViewModel.kt, feature/travel/src/main/java/com/yapp/ndgl/feature/travel/navigation/TravelEntry.kt, navigation/src/main/java/com/yapp/ndgl/navigation/Route.kt
FollowTravel 네비게이션 시 days 파라미터 추가 (travelId: Int → Long), 관련 콜백 및 Route 정의 업데이트.

Sequence Diagram(s)

sequenceDiagram
    participant UI as FollowTravelScreen
    participant VM as FollowTravelViewModel
    participant Repo as TravelTemplateRepository
    participant API as TravelTemplateApi
    participant Data as Backend

    UI->>VM: 초기화 (travelId, days)
    activate VM
    
    VM->>VM: loadTravelTemplateItinerary() 시작
    loop 각 day (1..days)
        VM->>Repo: getTravelTemplateItinerary(travelId, day)
        activate Repo
        Repo->>API: getTravelTemplateItinerary(travelId, day)
        activate API
        API->>Data: GET /api/v1/travel-templates/{id}/itinerary?day={day}
        Data-->>API: TravelTemplateItinerary
        API-->>Repo: BaseResponse<TravelTemplateItinerary>
        deactivate API
        Repo-->>VM: TravelTemplateItinerary
        deactivate Repo
        VM->>VM: Itinerary로 변환 및 상태 업데이트
    end

    par 병렬 요청
        VM->>Repo: getTravelTemplateContentInfo(travelId)
        activate Repo
        Repo->>API: getTravelTemplateContentInfo(travelId)
        activate API
        API->>Data: GET /api/v1/travel-templates/{id}/content-card
        Data-->>API: TravelTemplateContentInfo
        API-->>Repo: BaseResponse<TravelTemplateContentInfo>
        deactivate API
        Repo-->>VM: TravelTemplateContentInfo
        deactivate Repo
        VM->>VM: ContentInfo로 변환 및 상태 업데이트
    end
    
    deactivate VM
    VM-->>UI: 상태 업데이트 (itineraries, contentInfo)
    activate UI
    UI->>UI: 탭 및 장소 정보 렌더링
    deactivate UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 주요 변경사항인 여행 템플릿 API 연동을 명확하게 설명하고 있으며, 파일 목록이나 불필요한 내용 없이 간결하고 구체적입니다.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/NDGL-68

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

@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 `@core/util/src/main/java/com/yapp/ndgl/core/util/CountryCodeUtil.kt`:
- Around line 5-9: The String.toCountryName() function can throw
IllformedLocaleException when setRegion() receives lowercase or invalid input;
to fix it, normalize the receiver by trimming and converting to uppercase (e.g.,
call uppercase()) before passing to Locale.Builder().setRegion(), and wrap the
Locale.Builder()/setRegion()/build() and locale.displayCountry access in a
try-catch that catches IllformedLocaleException (and any other relevant
exceptions) and returns an empty string on error; ensure you still return "" for
non-2-character inputs and reference the String.toCountryName() function and
Locale.Builder().setRegion() locations when making the change.

In
`@data/travel/src/main/java/com/yapp/ndgl/data/travel/model/TravelTemplateItinerary.kt`:
- Line 16: Rename the misspelled property travlerTips to travelerTips in the
TravelTemplateItinerary data class (TravelTemplateItinerary.kt); update any
references/usages and constructor parameters to the new name, and if this class
is serialized/deserialized ensure you add or update `@SerialName` or migration
handling if other modules expect the old name so deserialization remains
compatible.

In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/datepicker/DatePickerScreen.kt`:
- Around line 133-156: The button is currently marked ACTIVE whenever
state.isDateSelected is true, even if state.isInsufficientDuration is also true;
update the NDGLCTAButton status logic so the button is ACTIVE only when
isDateSelected is true AND isInsufficientDuration is false (otherwise use
DISABLED). Locate the NDGLCTAButton instantiation in DatePickerScreen and change
the status expression to combine DatePickerState.isDateSelected and
!DatePickerState.isInsufficientDuration; also ensure clickCompleteButton is not
triggered when the button is disabled (rely on the button component to ignore
clicks when status is DISABLED or guard the handler if needed).

In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/PlacePhoto.kt`:
- Around line 8-9: The computed property aspectRatio in PlacePhoto (val
aspectRatio: Float get() = width.toFloat() / height.toFloat()) can produce
Infinity or NaN when height is 0 or non‑positive; update the getter to
defensively handle height <= 0 by returning a safe finite positive fallback (for
example 1f or max(1f, width.toFloat())) and/or clamp the result to a finite
positive value so Modifier.aspectRatio() always receives a valid positive float;
change only the aspectRatio getter in PlacePhoto.kt to perform the check and
return the safe value.

In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/travel/TravelViewModel.kt`:
- Around line 23-25: In clickTravel, stop ignoring the travelId and remove the
hardcoded postSideEffect call; call
postSideEffect(TravelSideEffect.NavigateToFollowTravel(travelId.toLong(),
<followId>)) using the actual travelId passed to clickTravel and convert it with
.toLong(), or alternatively change TravelIntent.ClickTravel.travelId in
TravelContract.kt from Int to Long so types align; update any callers of
ClickTravel accordingly to keep types consistent and ensure
NavigateToFollowTravel receives the correct long travelId instead of the
hardcoded (2, 1).
🧹 Nitpick comments (14)
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/placedetail/component/PlaceInfoRow.kt (2)

42-44: clip(CircleShape)이 trailing 아이콘에 불필요해 보입니다.

배경(background)이 없는 상태에서 clip(CircleShape)만 적용하면, 아이콘의 시각적 콘텐츠가 원형으로 잘릴 수 있습니다. chevron 아이콘의 경우 모서리 부분이 잘릴 수 있습니다. 의도된 동작이 아니라면 제거를 권장합니다.

제안
                 modifier = Modifier
-                    .size(24.dp)
-                    .clip(CircleShape),
+                    .size(24.dp),

27-50: content가 남은 공간을 채우지 않아 chevron이 오른쪽 끝에 배치되지 않을 수 있습니다.

현재 RowhorizontalArrangement = spacedBy(8.dp)만 적용되어 있어, content 영역이 좁을 경우 trailing chevron 아이콘이 Row 끝이 아닌 content 바로 옆에 위치합니다. 호출부에서 Modifier.weight(1f)를 content에 적용해야 올바르게 정렬됩니다. 만약 chevron을 항상 오른쪽 끝에 두는 것이 의도라면, Spacer(Modifier.weight(1f))를 content와 chevron 사이에 추가하거나 Arrangement.SpaceBetween 활용을 고려해 보세요.

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/datepicker/DatePickerScreen.kt (1)

113-158: 중첩된 Column 단순화 고려

외부 Column(Line 113-158)은 padding(top = 24.dp, bottom = 16.dp)만 적용하고, 내부 Column(Line 118-157)이 실제 콘텐츠를 담고 있습니다. 두 Column 모두 fillMaxSize()를 사용하며, 하나로 합쳐도 동일한 레이아웃을 구현할 수 있습니다.

단일 Column로 병합하는 예시
-            Column(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .padding(top = 24.dp, bottom = 16.dp),
-            ) {
-                Column(
-                    modifier = Modifier
-                        .fillMaxSize()
-                        .padding(horizontal = 24.dp),
-                ) {
+            Column(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .padding(top = 24.dp, bottom = 16.dp)
+                    .padding(horizontal = 24.dp),
+            ) {
                     CalendarView(
                         ...
                     )
                     ...
                     NDGLCTAButton(
                         ...
                     )
-                }
-            }
+            }
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/PlaceType.kt (1)

20-26: RESTAURANTCAFE가 동일한 색상(etcOrange)을 사용합니다.

의도된 디자인이라면 무시해주세요. 만약 두 타입의 시각적 구분이 필요하다면 별도 색상 할당을 고려해보세요.

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/PriceRange.kt (1)

7-8: formattedPriceRange에서 endPrice의 통화 기호가 누락되어 있습니다.

현재 "€5~15" 형태로 출력되는데, startPriceendPrice의 통화가 다를 경우 오해의 소지가 있습니다. 동일 통화가 보장된다면 현재 형태도 괜찮지만, 방어적으로 endPrice.symbol도 포함하거나 통화 일치 여부를 검증하는 것을 고려해보세요.

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/OpeningHours.kt (1)

13-27: 파싱 로직은 잘 동작하지만, 입력 형식에 대한 암묵적 의존이 있습니다.

", ""~" 구분자에 의존하는 파싱이므로, 서버 응답 형식이 변경되면 조용히 빈 periods를 반환할 수 있습니다. 현 단계에서는 문제없지만, 추후 API 연동 시 구조화된 JSON 응답(배열 형태)으로 대체하는 것이 더 안정적입니다.

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/placedetail/component/PlaceInfoTab.kt (1)

99-114: openingHoursestimatedDuration 모두 동일한 ic_24_clock 아이콘 사용

두 행이 연속으로 표시될 때 동일한 시계 아이콘이 반복되어 사용자가 구분하기 어려울 수 있습니다. estimatedDuration에 다른 아이콘(예: 타이머/모래시계)을 사용하는 것을 고려해 보세요.

feature/travel/src/main/java/com/yapp/ndgl/feature/travel/placedetail/PlaceDetailScreen.kt (1)

254-264: 사진 분할 로직이 매 recomposition마다 재계산됩니다

foldIndexedmutableListOf를 사용한 좌/우 사진 분할이 composable 내부에서 매번 실행됩니다. remember로 감싸거나 partition 사용을 권장합니다.

♻️ partition 사용 제안
-                            val (leftPhotos, rightPhotos) = state.photos.foldIndexed(
-                                initial = mutableListOf<PlacePhoto>() to mutableListOf<PlacePhoto>(),
-                            ) { index, lists, photo ->
-                                if (index % 2 == 0) {
-                                    lists.first.add(photo)
-                                } else {
-                                    lists.second.add(photo)
-                                }
-                                lists
-                            }
+                            val leftPhotos = state.photos.filterIndexed { index, _ -> index % 2 == 0 }
+                            val rightPhotos = state.photos.filterIndexed { index, _ -> index % 2 != 0 }
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/model/TransportType.kt (1)

15-17: else 분기에서 TRANSIT, BUS, TRAIN 등 모든 교통수단이 CAR로 매핑됩니다.

TransportCategory에는 DRIVING, TRANSIT, BICYCLING, TAXI, TWO_WHEELER, FERRY, FLIGHT 등 7개 카테고리가 있지만, WALKING 외에는 전부 CAR로 폴백됩니다. TransportType에 이미 BUS, TRAIN이 정의되어 있으므로 최소한 TRANSIT 매핑은 추가하는 것이 좋겠습니다.

제안
 internal fun TransportCategory.toTransportType(): TransportType = when (this) {
     TransportCategory.WALKING -> TransportType.WALK
-    else -> TransportType.CAR // FIXME: 교통수단 변경
+    TransportCategory.TRANSIT -> TransportType.TRAIN
+    TransportCategory.DRIVING, TransportCategory.TAXI -> TransportType.CAR
+    else -> TransportType.CAR // FIXME: BICYCLING, TWO_WHEELER, FERRY, FLIGHT 매핑 추가
 }
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/travel/TravelContract.kt (1)

11-18: travelId 타입 불일치: Intent는 Int, SideEffect는 Long

ClickTravel.travelIdInt(Line 12)인 반면, NavigateToFollowTravel.travelIdLong(Line 17)입니다. Route.FollowTravelFollowTravelViewModel이 모두 Long을 사용하고 있으므로, Intent 쪽도 Long으로 맞추면 향후 실제 travelId를 전달할 때 타입 변환 누락 위험을 방지할 수 있습니다.

제안
 sealed interface TravelIntent : UiIntent {
-    data class ClickTravel(val travelId: Int) : TravelIntent
+    data class ClickTravel(val travelId: Long) : TravelIntent
     data class ClickTravelDetail(val travelId: Int) : TravelIntent
 }
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/followtravel/FollowTravelViewModel.kt (2)

107-121: days, countryCode 등 이름 섀도잉으로 인한 가독성 저하

TravelTemplateContentInfo.toContentInfo()는 ViewModel의 멤버 함수이면서 동시에 TravelTemplateContentInfo의 확장 함수입니다. Line 112의 days는 Kotlin 스코핑 규칙에 따라 확장 리시버(TravelTemplateContentInfo.days)를 참조하지만, ViewModel의 생성자 파라미터 days와 이름이 같아 혼동될 수 있습니다.

명시적으로 작성하면 의도가 더 명확해집니다:

제안
-    private fun TravelTemplateContentInfo.toContentInfo(): ContentInfo = ContentInfo(
-        country = countryCode.toCountryName(),
-        city = city,
-        budgetPerPerson = Budget(budgetPerPerson ?: 0),
-        nights = nights,
-        days = days,
+    private fun TravelTemplateContentInfo.toContentInfo(): ContentInfo = ContentInfo(
+        country = this.countryCode.toCountryName(),
+        city = this.city,
+        budgetPerPerson = Budget(this.budgetPerPerson ?: 0),
+        nights = this.nights,
+        days = this.days,

46-48: 에러 처리 미구현 (FIXME/TODO)

loadTravelTemplateItinerary(Line 47)와 loadTravelTemplateContentInfo(Line 64) 모두 onFailure에서 에러를 무시하고 있습니다. 현재 상태에서는 API 실패 시 빈 화면이 표시되며 사용자에게 어떤 피드백도 없습니다. 로딩/에러 상태를 FollowTravelState에 추가하여 UI에서 적절히 처리할 수 있도록 하는 것을 권장합니다.

로딩/에러 상태 관리를 포함한 구현을 생성해 드릴까요?

Also applies to: 63-65

navigation/src/main/java/com/yapp/ndgl/navigation/Route.kt (1)

17-24: travelId 타입 불일치: FollowTravelLong, TravelDetailInt

FollowTravel.travelIdLong으로 업데이트되었지만, TravelDetail.travelId는 여전히 Int입니다. 동일한 travel ID가 두 Route에서 사용될 수 있다면, 타입을 통일하는 것이 안전합니다. 서버에서 Long 범위의 ID를 반환할 경우 TravelDetail에서 잘릴 수 있습니다.

제안
     `@Serializable`
-    data class TravelDetail(val travelId: Int) : Route
+    data class TravelDetail(val travelId: Long) : Route
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/followtravel/FollowTravelScreen.kt (1)

150-167: 동일한 itinerary 접근 패턴 중복

Line 156의 state.itineraries.getOrNull(state.selectedDay - 1)과 Line 169의 동일한 접근이 반복됩니다. LazyListScope 레벨에서 한 번만 계산하여 map과 places 모두에 사용할 수 있습니다. 다만 서로 다른 item 블록에서 참조하므로 현재 구조도 문제없습니다.

Comment thread core/util/src/main/java/com/yapp/ndgl/core/util/CountryCodeUtil.kt
Comment on lines +133 to 156
if (state.isInsufficientDuration) {
Spacer(Modifier.height(24.dp))
Text(
stringResource(
R.string.date_picker_error_insufficient,
),
color = NDGLTheme.colors.red500,
style = NDGLTheme.typography.bodySmMedium,
)
}
Spacer(Modifier.weight(1f))
NDGLCTAButton(
modifier = Modifier
.fillMaxWidth(),
type = NDGLCTAButtonAttr.Type.PRIMARY,
size = NDGLCTAButtonAttr.Size.LARGE,
status = if (state.isDateSelected) {
NDGLCTAButtonAttr.Status.ACTIVE
} else {
NDGLCTAButtonAttr.Status.DISABLED
},
label = stringResource(R.string.date_picker_complete),
onClick = clickCompleteButton,
)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "*DatePickerViewModel*" -o -name "*DatePickerState*" | head -20

Repository: YAPP-Github/27th-App-Team-1-Android

Length of output: 172


🏁 Script executed:

fd "DatePickerViewModel" -t f

Repository: YAPP-Github/27th-App-Team-1-Android

Length of output: 170


🏁 Script executed:

fd "DatePickerState" -t f

Repository: YAPP-Github/27th-App-Team-1-Android

Length of output: 61


🏁 Script executed:

cat ./feature/travel/src/main/java/com/yapp/ndgl/feature/travel/datepicker/DatePickerViewModel.kt

Repository: YAPP-Github/27th-App-Team-1-Android

Length of output: 3957


🏁 Script executed:

fd "DatePickerState" -t f --search-path feature/travel

Repository: YAPP-Github/27th-App-Team-1-Android

Length of output: 61


🏁 Script executed:

find ./feature/travel -name "*.kt" | xargs grep -l "data class DatePickerState\|class DatePickerState"

Repository: YAPP-Github/27th-App-Team-1-Android

Length of output: 171


🏁 Script executed:

cat ./feature/travel/src/main/java/com/yapp/ndgl/feature/travel/datepicker/DatePickerContract.kt

Repository: YAPP-Github/27th-App-Team-1-Android

Length of output: 1806


버튼 상태 로직 수정 필요

DatePickerState의 계산 속성들을 확인한 결과, 리뷰 지적이 타당합니다:

  • isDateSelected: startDate와 endDate가 모두 null이 아닐 때 true
  • isInsufficientDuration: startDate와 endDate가 모두 null이 아니면서 기간이 tripDays 미만일 때 true

따라서 사용자가 날짜를 선택했지만 기간이 부족한 경우(isDateSelected == true && isInsufficientDuration == true)가 실제로 발생할 수 있으며, 현재 코드는 이 경우 에러 메시지와 함께 활성화된 버튼을 표시합니다.

버튼 비활성화 조건에 isInsufficientDuration을 추가하여 수정해야 합니다:

수정사항
                     status = if (state.isDateSelected) {
-                        NDGLCTAButtonAttr.Status.ACTIVE
+                        if (state.isInsufficientDuration) {
+                            NDGLCTAButtonAttr.Status.DISABLED
+                        } else {
+                            NDGLCTAButtonAttr.Status.ACTIVE
+                        }
                     } else {
                         NDGLCTAButtonAttr.Status.DISABLED
                     },
🤖 Prompt for AI Agents
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/datepicker/DatePickerScreen.kt`
around lines 133 - 156, The button is currently marked ACTIVE whenever
state.isDateSelected is true, even if state.isInsufficientDuration is also true;
update the NDGLCTAButton status logic so the button is ACTIVE only when
isDateSelected is true AND isInsufficientDuration is false (otherwise use
DISABLED). Locate the NDGLCTAButton instantiation in DatePickerScreen and change
the status expression to combine DatePickerState.isDateSelected and
!DatePickerState.isInsufficientDuration; also ensure clickCompleteButton is not
triggered when the button is disabled (rely on the button component to ignore
clicks when status is DISABLED or guard the handler if needed).

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.

날짜가 부족할 때 비활성화되는 것은 기획에 해당하지 않음

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@mj010504, 이해했습니다! 날짜가 부족할 때에도 버튼이 활성화되어 사용자가 진행할 수 있도록 하는 것이 의도된 기획이군요. 에러 메시지를 표시하면서도 사용자가 계속 진행할 수 있게 하는 UX 디자인으로 확인했습니다. 명확히 알려주셔서 감사합니다!


✏️ Learnings added
Learnt from: mj010504
Repo: YAPP-Github/27th-App-Team-1-Android PR: 23
File: feature/travel/src/main/java/com/yapp/ndgl/feature/travel/datepicker/DatePickerScreen.kt:133-156
Timestamp: 2026-02-16T12:26:32.687Z
Learning: DatePickerScreen에서 날짜 기간이 부족할 때(isInsufficientDuration == true) 에러 메시지를 표시하지만, 버튼은 비활성화하지 않고 사용자가 계속 진행할 수 있도록 하는 것이 제품 기획입니다.

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.

@mj010504 mj010504 self-assigned this Feb 16, 2026
@mj010504 mj010504 merged commit 2b43ad8 into develop Feb 16, 2026
2 checks passed
@mj010504 mj010504 deleted the feature/NDGL-68 branch February 16, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant