Skip to content

Conversation

@doyeonk429
Copy link
Contributor

🚀 PR 개요

와인 상세 조회 화면에서 빈티지 선택 플로우 적용

💡 PR 유형

  • ✨ Feature (기능 추가)
  • 🐞 Bugfix (버그 수정)
  • 🔥 Hotfix (긴급 수정)
  • 🔧 Refactor (코드 리팩토링)
  • ⚙️ Chore (환경 설정)
  • 📝 Docs (문서 작성 및 수정)

✏️ 변경 사항 요약

  • 빈티지 선택 화면과 데이터 전달
  • 선택된 빈티지에 따른 UI 변경
  • 테스트를 위한 임시 테이스팅노트 생성 과정에서 빈티지 항목 적용(와인 상세 > 테노 작성하러 가기 에서만 적용됨)

🔗 관련 이슈

🧪 테스트 내역

  • 브라우저/기기에서 동작 확인
  • 엣지 케이스 테스트 완료
  • 기존 기능 영향 없음

🎨 스크린샷 또는 시연 영상 (선택)

Simulator.Screen.Recording.-.iPhone.16.-.2025-08-31.at.21.42.19.mp4

✅ PR 체크리스트

  • 커밋 메시지가 명확합니다
  • Merge 대상 Branch가 develop입니다
  • PR 제목이 컨벤션에 맞습니다
  • 관련 이슈 번호를 작성했습니다
  • 기능이 정상적으로 작동합니다
  • 불필요한 코드를 제거했습니다

💬 추가 설명 or 리뷰 포인트 (선택)

리뷰어가 중점적으로 봐야 할 부분이나 설명이 필요한 내용을 자유롭게 작성해주세요.

  • 와인 상세 화면 코드 전반적인 개선 필요함. 특히, 하단 리뷰 레이아웃 계산 방식에 문제점이 있으나 원인 규명도 쉽지 않음. 추후 별도 이슈 파서 작업할 예정.

@doyeonk429 doyeonk429 self-assigned this Aug 31, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 31, 2025

📝 Walkthrough

Summary by CodeRabbit

  • 신기능

    • 빈티지 선택 기능 추가: 연도별 리스트(현재 연도~1970)에서 선택하고 상세/리뷰 화면에 반영.
    • 테이스팅 노트 작성 시 빈티지 정보를 함께 전송.
  • 개선

    • 상세 화면과 작성 플로우 헤더에 와인명+빈티지 표시, 빈티지 미선택 시 “빈티지 선택” 안내.
    • 빈티지 미선택 상태에서 테이스팅 노트 이동 시 안내 토스트 노출.
    • 평균 테이스팅 노트 영역의 빈 상태/콘텐츠 노출 로직 개선 및 안내 문구 강화.
    • 위시리스트(좋아요)와 상세 데이터가 선택한 빈티지에 맞춰 연동.

Walkthrough

빈티지 기반 흐름을 추가했다. 빈티지 선택 화면이 동적으로 연도를 생성하고 선택 콜백을 전달한다. 선택된 빈티지는 상세/테이스팅 노트 화면과 네트워크 호출(위시리스트/노트 작성)에 반영된다. AverageTastingNoteView는 빈티지 유무에 따라 콘텐츠/메시지 표시를 전환하며, 관련 configure 시그니처가 변경됐다.

Changes

Cohort / File(s) Summary
CommonUI: AverageTastingNoteView 리팩터링 및 API 변경
DE/DE/Sources/Core/CommonUI/View/AverageTastingNoteView.swift
콘텐츠 컨테이너 도입, 레이아웃 재구성, noTastingNote 메시지 추가/토글, 빈티지 기반 표시 로직 도입. configure 시그니처에 vintage: Int? 추가. public let writeNewTastingNoteBtn 공개 속성 추가.
CommonUI: VintageInfoView 초기 텍스트 변경
DE/DE/Sources/Core/CommonUI/View/VintageInfoView.swift
기본 연도 텍스트를 "빈티지 선택"으로 초기화.
Search Feature: 상세 화면 빈티지 흐름 연결
DE/DE/Sources/Features/Search/ViewControllers/WineDetailViewController.swift
vintage: Int? 저장, 빈티지 선택 콜백 연동, UI 반영, API 재요청 시 빈티지 전달, 위시리스트/리뷰/테이스팅 노트 호출에 빈티지 적용, AverageTastingNoteView.configure 호출 변경.
TastingNote 모델 확장
DE/DE/Sources/Features/TastingNote/Models/TNWineDataManager.swift
vintage 속성/초기화/업데이트/리셋 확장.
TastingNote 공통 상세 VC 연결
DE/DE/Sources/Features/TastingNote/ViewControllers/Common/TNWineDetailViewController.swift
vintage: Int? 추가, AverageTastingNoteView.configure에 빈티지 전달.
TastingNote 생성 플로우: 헤더 타이틀 및 바인딩 보강
.../CreateVCs/ChooseWineColorViewController.swift, .../CreateVCs/NoseTestVC.swift, .../CreateVCs/RatingWineViewController.swift, .../CreateVCs/RecordGraphViewController.swift, .../CreateVCs/TastedDateViewController.swift
헤더 타이틀에 빈티지 포함 표시. 색상 선택 화면에 품종 텍스트 표시 정리. Rating 화면에서 노트 작성 DTO에 vintage 포함. 일부 헤더 API 사용 확장(스타일/설명 인자).
Vintage 선택 화면 개선
DE/DE/Sources/Features/Vintage/VintageTableViewController.swift
현재 연도 기준 1970까지 10년 단위 섹션 동적 생성, 선택 콜백 onYearSelected 도입, 선택 시 팝/콜백 호출.
Network: TastingNote 서비스 시그니처 변경
DE/DE/Sources/Network/TastingNote/Service/TastingNoteService.swift
makePostNoteDTOvintage: Int 추가, DTO에 vintageYear 설정.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant WDV as WineDetailViewController
  participant VTV as VintageTableViewController
  participant API as Backend API
  participant ATV as AverageTastingNoteView

  U->>WDV: 빈티지 선택 버튼 탭
  WDV->>VTV: present()
  U->>VTV: 연도 선택
  VTV-->>WDV: onYearSelected(year)
  WDV->>WDV: self.vintage = year<br/>UI 업데이트(VintageInfoView)
  WDV->>API: callWineDetailAPI(wineId, vintage=year)
  API-->>WDV: WineDetailResponse(vintageYear, avgTN, ...)
  WDV->>ATV: configure(avgData, vintage=year)
  ATV->>ATV: hasContent 판단/표시 전환
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant RVC as RatingWineViewController
  participant TNS as TastingNoteService
  participant API as Backend API

  U->>RVC: 테이스팅 노트 작성 완료
  RVC->>TNS: makePostNoteDTO(wineId, vintage, ...)
  TNS-->>RVC: TastingNoteRequestDTO(vintageYear= vintage)
  RVC->>API: POST /tasting-notes (DTO)
  API-->>RVC: 201 Created
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
기본 빈티지 연도 설정 (#178)
빈티지 선택 정보 전달(viewController 간) (#178)
빈티지 연도 선택 후 API 재요청 (#178)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
공개 버튼 속성 추가 writeNewTastingNoteBtn (DE/DE/Sources/Core/CommonUI/View/AverageTastingNoteView.swift) 이슈 #178의 범위(빈티지 플로우)와 직접적 연계가 불분명한 공개 API 표면 확대입니다.
헤더 API 확장: setTitleLabel에 스타일/설명 인자 추가 사용 (DE/.../CreateVCs/TastedDateViewController.swift) 빈티지 선택 흐름과 무관한 헤더 컴포넌트 API 변경/사용 확장으로 보입니다.
품종 텍스트 바인딩 및 공백 정리 추가 (DE/.../CreateVCs/ChooseWineColorViewController.swift) 빈티지 플로우 외 UI 개선으로 보이며 이슈 목표와 직접 연결되지 않습니다.
NoseTestVC에서 설명 스타일 지정 추가 (DE/.../CreateVCs/NoseTestVC.swift) 빈티지 데이터 흐름과 무관한 시각적 스타일 변경입니다.

Possibly related PRs

Suggested reviewers

Poem

해마다 빛 바랜 포도 향기, 년도를 품고 돌아오네 🍇
리스트에 싹, 노트엔 딱—빈티지 따라 흐름이 붙고,
화면은 말하네 “선택해 주세요”,
콜백은 속삭여 “이 해로 가자고.”
한 잔의 코드, 올해는 더 정확하게!

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/issue-178

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai bot requested review from dlguszoo and yeseonglee August 31, 2025 13:04
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: 1

Caution

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

⚠️ Outside diff range comments (4)
DE/DE/Sources/Core/CommonUI/View/VintageInfoView.swift (1)

72-76: 오토레이아웃 충돌: bottom 제약 중복

vintageChangeView에 동일한 축의 bottom 제약이 2개 있어(일반 bottom + inset bottom) 런타임 경고/우선순위 충돌 가능성이 큽니다. 하나로 통일하세요.

다음처럼 정리하면 됩니다.

         vintageChangeView.snp.makeConstraints {
             $0.top.equalTo(title.snp.bottom).offset(10)
-            $0.leading.trailing.bottom.equalToSuperview()
-            $0.bottom.equalToSuperview().inset(24)
+            $0.leading.trailing.equalToSuperview()
+            $0.bottom.equalToSuperview().inset(24)
         }
DE/DE/Sources/Features/TastingNote/Models/TNWineDataManager.swift (1)

19-28: 접근 제어 컴파일 에러: internal 타입에 public 이니셜라이저 선언

class TNWineDataManager는 기본 internal인데 public init은 허용되지 않습니다. 빌드 실패합니다. public을 제거하세요(또는 타입을 public로 올리되 전체 영향 검토 필요).

-    public init(
+    init(
         wineId: Int = 0,
         wineName: String = "",
         vintage: Int = 0,
         ...
DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/NoseTestVC.swift (1)

46-53: 타이틀에 ‘0’ 빈티지 노출 방지 및 초기/상단바 타이틀 일관화

vintage=0 노출 가능성과 화면 전환 타이밍마다 다른 타이틀 구성 로직이 보입니다. 조건부로 붙이고 세 군데 모두 동일하게 맞추세요.

-        let fullName = "\(wineData.wineName) \(wineData.vintage)"
-        topView.header.setTitleLabel(title: fullName,
+        let title = wineData.vintage > 0 ? "\(wineData.wineName) \(wineData.vintage)" : wineData.wineName
+        topView.header.setTitleLabel(title: title,
                                      titleStyle: AppTextStyle.KR.subtitle1,
                                      titleColor: AppColor.purple100,
                                      description: despText,
                                      descriptionStyle: AppTextStyle.KR.head,
                                      descriptionColor: AppColor.black)
...
-        topView.header.setTitleLabel(title: wineData.wineName,
+        topView.header.setTitleLabel(title: wineData.vintage > 0 ? "\(wineData.wineName) \(wineData.vintage)" : wineData.wineName,
                                      titleStyle: AppTextStyle.KR.subtitle1,
                                      titleColor: AppColor.purple100,
                                      description: despText,
                                      descriptionStyle: AppTextStyle.KR.head,
                                      descriptionColor: AppColor.black)
...
-        smallTitleLabel = navigationBarManager.setNReturnTitle(
-            to: navigationItem,
-            title: wineData.wineName,
-            textColor: AppColor.black
-        )
+        smallTitleLabel = navigationBarManager.setNReturnTitle(
+            to: navigationItem,
+            title: wineData.vintage > 0 ? "\(wineData.wineName) \(wineData.vintage)" : wineData.wineName,
+            textColor: AppColor.black
+        )

Also applies to: 84-90, 145-151

DE/DE/Sources/Features/TastingNote/ViewControllers/Common/TNWineDetailViewController.swift (1)

237-253: vintage 값 할당 누락으로 AverageTastingNoteView가 항상 ‘빈티지를 선택…’으로 보일 수 있습니다.

현재 self.vintage는 어디서도 설정되지 않은 상태로 configure(avgData, self.vintage)에 전달됩니다. 최소한 transformResponseData()에서 TNWineDataManager의 값을 반영하거나, 빈티지 선택 화면 콜백을 연결해야 합니다.

다음 패치를 통해 즉시 증상은 완화됩니다(0은 미선택으로 처리):

     func transformResponseData() {
         let wineResponse = wineData
         let tnResponse = tnManager
         self.wineId = wineResponse.wineId
         self.wineName = wineResponse.wineName
+        // vintage 주입(0은 미선택으로 간주)
+        self.vintage = (wineResponse.vintage > 0) ? wineResponse.vintage : nil

또한, 빈티지 선택 화면에서 선택 콜백을 연결해 반영하세요:

     func setButtonAction() {
         vintageInfoView.tabAction = { [weak self] in
             
             let vc = VintageTableViewController()
             vc.hidesBottomBarWhenPushed = true
+            vc.onYearSelected = { [weak self] year in
+                self?.vintage = year
+                // 필요 시 공유 매니저에도 반영
+                self?.wineData.vintage = year
+                // 화면 갱신
+                self?.transformResponseData()
+            }
             self?.navigationController?.pushViewController(vc, animated: true)
         }
     }

필요하시면 위 변경을 포함한 커밋 패치를 더 만들어드릴게요.

🧹 Nitpick comments (15)
DE/DE/Sources/Core/CommonUI/View/VintageInfoView.swift (1)

53-57: 플레이스홀더 도입은 좋습니다. 다만 선택 전/후 시각 구분을 권장합니다.

미선택(플레이스홀더)일 때는 연한 컬러(gray50 등), 선택 후에는 현재처럼 진한 컬러(gray100)로 차등 주면 가독성이 좋아집니다.

DE/DE/Sources/Features/TastingNote/Models/TNWineDataManager.swift (1)

11-11: vintage=0을 ‘미선택’으로 쓰는 컨벤션 정리 필요

UI와 네트워크 레이어에서 0이 그대로 노출/전송될 수 있습니다(예: 타이틀 “... 0”). 당장 구조 변경이 어렵다면, 표시/전송 시점에서 vintage > 0만 사용하도록 가드해 주세요(아래 각 VC/뷰에 별도 제안 포함). 추후에는 Int?로 전환을 고려하면 혼동이 줄어듭니다.

Also applies to: 22-22, 63-63

DE/DE/Sources/Core/CommonUI/View/AverageTastingNoteView.swift (2)

71-75: 빈 상태 라벨 오토레이아웃 보완

noTastingNote에 trailing 제약이 없어 긴 문구에서 잘림/오버플로가 날 수 있습니다. numberOfLines도 0으로 열어두세요.

-    private let noTastingNote = UILabel().then {
-        $0.isHidden = true
-    }
+    private let noTastingNote = UILabel().then {
+        $0.isHidden = true
+        $0.numberOfLines = 0
+        $0.textAlignment = .left
+    }
...
-        noTastingNote.snp.makeConstraints {
-            $0.top.equalTo(title.snp.bottom).offset(10)
-            $0.leading.equalToSuperview()
-            $0.bottom.equalToSuperview().inset(24)
-        }
+        noTastingNote.snp.makeConstraints {
+            $0.top.equalTo(title.snp.bottom).offset(10)
+            $0.horizontalEdges.equalToSuperview()
+            $0.bottom.equalToSuperview().inset(24)
+        }

101-118: 빈티지 0 처리 및 가독성 개선

현재 hasContent = (vintage != nil) && ...라서 vintage=0도 선택으로 간주됩니다. 0을 미선택으로 취급하도록 보완하고, 메시지 분기에도 동일 기준을 쓰면 일관성이 좋아집니다. (시그니처의 두 번째 파라미터도 라벨을 붙이면 호출부 가독성이 개선됩니다.)

-    public func configure(_ model: WineAverageTastingNoteModel, _ vintage: Int?) {
-        let hasContent = (vintage != nil) && !model.wineNoseText.isEmpty
+    public func configure(_ model: WineAverageTastingNoteModel, vintage: Int?) {
+        let hasSelectedVintage = (vintage ?? 0) > 0
+        let hasContent = hasSelectedVintage && !model.wineNoseText.isEmpty
...
-        } else {
+        } else {
             AppTextStyle.KR.body3.apply(to: noseContents, text: "", color: AppColor.gray100)
             AppTextStyle.KR.body3.apply(to: palateContents, text: "", color: AppColor.gray100)
 
-            let message = (vintage == nil) ? "빈티지를 선택해 주세요." : "작성된 테이스팅 노트가 없습니다."
+            let message = (!hasSelectedVintage) ? "빈티지를 선택해 주세요." : "작성된 테이스팅 노트가 없습니다."
             AppTextStyle.KR.body2.apply(to: noTastingNote, text: message, color: AppColor.gray70)
         }

주의: 시그니처 변경 시 호출부 업데이트 필요(configure(avg, vintage: ...)).

DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/ChooseWineColorViewController.swift (2)

27-28: 빈티지 미선택(0)일 때 제목에 '0'이 노출되지 않도록 가드하세요.

TNWineDataManager.vintage가 기본값 0이면 "와인이름 0"으로 표시됩니다. UX를 위해 0이면 빈티지표기를 생략하세요.

다음과 같이 처리하면 안전합니다:

-        let fullName = "\(wineData.wineName) \(wineData.vintage)"
-        colorView.header.setTitleLabel(title: fullName)
+        let fullName = wineData.vintage > 0
+            ? "\(wineData.wineName) \(wineData.vintage)"
+            : wineData.wineName
+        colorView.header.setTitleLabel(title: fullName)

34-34: 품종 문자열 공백/구두점 정규화 로직을 간결하게 합시다.

" ," 한 케이스만 치환하면 다른 케이스(" , ", ",,")는 놓칩니다. split+trim+join이 견고합니다.

-        colorView.infoView.typeContents.text = wineData.variety.replacingOccurrences(of: " ,", with: ",")
+        let normalizedVariety = wineData.variety
+            .split(separator: ",")
+            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
+            .joined(separator: ", ")
+        colorView.infoView.typeContents.text = normalizedVariety
DE/DE/Sources/Features/TastingNote/ViewControllers/Common/TNWineDetailViewController.swift (1)

19-19: 불필요한 옵셔널 nil 초기화를 제거하세요.

var vintage: Int?는 기본이 nil입니다. SwiftLint 경고도 제거됩니다.

-    var vintage: Int? = nil
+    var vintage: Int?
DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/RatingWineViewController.swift (3)

32-33: 제목에 ‘0’ 빈티지가 노출되지 않도록 동일한 가드를 적용하세요.

다른 화면과 동일하게 0이면 빈티지 표기를 생략하면 일관성이 좋아집니다.

-        let fullName = "\(wineData.wineName) \(wineData.vintage)"
-        rView.header.setTitleLabel(title: fullName)
+        let fullName = wineData.vintage > 0
+            ? "\(wineData.wineName) \(wineData.vintage)"
+            : wineData.wineName
+        rView.header.setTitleLabel(title: fullName)

147-160: 서버 전송 전 vintage 유효성(>0) 검증을 추가하세요.

현재 빈티지 선택이 보장되지 않은 경로가 있다고 하셨습니다. vintage=0 전송 시 서버 4xx 가능성이 있습니다. UX 메시지와 함께 조기 반환이 안전합니다.

     private func postCreateTastingNote() async throws {
+        guard wineData.vintage > 0 else {
+            showToastMessage(message: "빈티지를 먼저 선택해 주세요.", yPosition: view.frame.height * 0.75)
+            return
+        }
         let createNoteDTO = networkService.makePostNoteDTO(
             wineId: wineData.wineId,
             vintage: wineData.vintage,

서버가 0을 허용(미상)하는지 확인 필요합니다. 허용한다면 가드는 토스트 없이 경고만으로 완화할 수도 있어요.


32-33: 여러 VC에서 반복되는 ‘와인이름 + 빈티지’ 포맷을 공통화합시다.

중복 로직이 늘어납니다. TNWineDataManager에 displayName 계산 프로퍼티를 두면 오타/불일치 방지에 도움이 됩니다.

예:

extension TNWineDataManager {
    var displayName: String {
        return vintage > 0 ? "\(wineName) \(vintage)" : wineName
    }
}

그 후 rView.header.setTitleLabel(title: wineData.displayName)처럼 사용.

Also applies to: 147-160

DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/TastedDateViewController.swift (1)

23-29: 제목에 ‘0’ 빈티지가 노출되지 않도록 가드 추가

다른 화면과 동일 기준으로 0일 때는 빈티지 표기를 생략하세요.

-        let fullName = "\(self.wineData.wineName) \(self.wineData.vintage)"
+        let fullName = self.wineData.vintage > 0
+            ? "\(self.wineData.wineName) \(self.wineData.vintage)"
+            : self.wineData.wineName
         tastedDateView.topView.setTitleLabel(title: fullName,
                                              titleStyle: AppTextStyle.KR.subtitle1,
                                              titleColor: AppColor.purple100,
                                              description: despText,
                                              descriptionStyle: AppTextStyle.KR.head,
                                              descriptionColor: AppColor.black)
DE/DE/Sources/Features/Vintage/VintageTableViewController.swift (1)

74-107: 데이터 생성 로직 개선 필요

현재 연도 계산과 루프 조건에서 몇 가지 개선이 필요합니다:

  1. currentDecadeStartYear가 현재 연도의 시작년도(예: 2020)로 초기화되어 있는데, 라인 96에서 여전히 currentDecadeStartYear를 사용하고 있어 혼동을 줄 수 있습니다.
  2. 라인 84-87의 guard 문은 실제로는 2020년대가 아직 1년만 지난 경우(예: 2020년)에만 발생하는 예외 케이스입니다. 이 부분을 더 명확하게 처리할 수 있습니다.

더 명확한 로직으로 개선:

 private func setupSectionData() {
     let currentYear = Calendar.current.component(.year, from: Date())
     let fixedStartYear = 1970
-    var currentDecadeStartYear = (currentYear / 10) * 10 // 예: 2025년 -> 2020년
+    var decadeStart = (currentYear / 10) * 10 // 예: 2025년 -> 2020년
     
     var dynamicSections: [Section<VintageItem>] = []
     
-    while currentDecadeStartYear >= fixedStartYear {
-        let startYear = currentDecadeStartYear
-        let endYear = (startYear == (currentYear / 10) * 10) ? (currentYear - 1) : (startYear + 9)
-        guard startYear <= endYear else {
-            currentDecadeStartYear -= 10
-            continue
-        }
+    while decadeStart >= fixedStartYear {
+        let startYear = decadeStart
+        let endYear = min(startYear + 9, currentYear - 1) // 현재 연도 이전까지만
+        
+        // 유효한 연도 범위가 있는 경우에만 섹션 추가
+        if startYear <= endYear {
+            let items = (startYear...endYear)
+                .reversed()
+                .map { year in
+                    VintageItem(year: year, score: 0.0)
+                }
         
-        let items = (startYear...endYear)
-            .reversed()
-            .map { year in
-                VintageItem(year: year, score: 0.0)
-            }
-        
-        let section = Section(
-            title: "\(currentDecadeStartYear)년대",
-            isExpanded: false,
-            items: items
-        )
-        dynamicSections.append(section)
+            let section = Section(
+                title: "\(decadeStart)년대",
+                isExpanded: false,
+                items: items
+            )
+            dynamicSections.append(section)
+        }
         
-        currentDecadeStartYear -= 10
+        decadeStart -= 10
     }
     
     self.sections = dynamicSections
     tableView.reloadData()
 }
DE/DE/Sources/Features/Search/ViewControllers/WineDetailViewController.swift (3)

19-19: 불필요한 nil 초기화 제거

옵셔널 변수를 선언할 때 nil로 명시적으로 초기화하는 것은 불필요합니다. Swift에서 옵셔널은 기본적으로 nil로 초기화됩니다.

-var vintage: Int? = nil
+var vintage: Int?

204-207: 사용자 경험 개선 제안

빈티지를 선택하지 않으면 테이스팅 노트를 작성할 수 없도록 제한하고 있습니다. 이는 비즈니스 요구사항에 따른 것으로 보이나, 토스트 메시지의 위치를 조정하면 더 좋을 것 같습니다.

토스트 메시지 위치를 더 일관성 있게 조정:

-self.showToastMessage(message: "빈티지를 선택해주세요.", yPosition: view.frame.height * 0.75)
+self.showToastMessage(message: "빈티지를 선택해주세요.", yPosition: view.safeAreaLayoutGuide.layoutFrame.height * 0.85)

362-366: 빈티지 표시 로직 개선 가능

빈티지 정보 표시 로직이 중복되어 있습니다. 더 간결하게 작성할 수 있습니다.

-if let year = self.vintage {
-    self.vintageInfoView.configure(with: "\(year)")
-} else {
-    self.vintageInfoView.configure(with: "빈티지 선택")
-}
+let vintageText = self.vintage.map { "\($0)" } ?? "빈티지 선택"
+self.vintageInfoView.configure(with: vintageText)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4693232 and e11fddc.

📒 Files selected for processing (12)
  • DE/DE/Sources/Core/CommonUI/View/AverageTastingNoteView.swift (4 hunks)
  • DE/DE/Sources/Core/CommonUI/View/VintageInfoView.swift (1 hunks)
  • DE/DE/Sources/Features/Search/ViewControllers/WineDetailViewController.swift (11 hunks)
  • DE/DE/Sources/Features/TastingNote/Models/TNWineDataManager.swift (6 hunks)
  • DE/DE/Sources/Features/TastingNote/ViewControllers/Common/TNWineDetailViewController.swift (2 hunks)
  • DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/ChooseWineColorViewController.swift (1 hunks)
  • DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/NoseTestVC.swift (1 hunks)
  • DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/RatingWineViewController.swift (2 hunks)
  • DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/RecordGraphViewController.swift (1 hunks)
  • DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/TastedDateViewController.swift (1 hunks)
  • DE/DE/Sources/Features/Vintage/VintageTableViewController.swift (6 hunks)
  • DE/DE/Sources/Network/TastingNote/Service/TastingNoteService.swift (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: doyeonk429
PR: Drink-Easy/DE_iOS#0
File: :0-0
Timestamp: 2025-08-30T13:14:55.580Z
Learning: 사용자 doyeonk429는 한국어로 코드 리뷰를 받기를 선호합니다.
🧬 Code graph analysis (8)
DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/ChooseWineColorViewController.swift (2)
DE/DE/Sources/Features/TastingNote/Views/ViewComponents/NoseEditTopView.swift (1)
  • setTitleLabel (92-131)
DE/DE/Sources/Features/TastingNote/Views/ViewComponents/Common/TopView.swift (1)
  • setTitleLabel (58-97)
DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/RecordGraphViewController.swift (2)
DE/DE/Sources/Features/TastingNote/Views/ViewComponents/NoseEditTopView.swift (1)
  • setTitleLabel (92-131)
DE/DE/Sources/Features/TastingNote/Views/ViewComponents/Common/TopView.swift (1)
  • setTitleLabel (58-97)
DE/DE/Sources/Features/TastingNote/ViewControllers/Common/TNWineDetailViewController.swift (1)
DE/DE/Sources/Core/CommonUI/View/AverageTastingNoteView.swift (1)
  • configure (101-121)
DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/RatingWineViewController.swift (2)
DE/DE/Sources/Features/TastingNote/Views/ViewComponents/Common/TopView.swift (1)
  • setTitleLabel (58-97)
DE/DE/Sources/Network/TastingNote/Service/TastingNoteService.swift (1)
  • makePostNoteDTO (28-56)
DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/TastedDateViewController.swift (1)
DE/DE/Sources/Features/TastingNote/Views/ViewComponents/Common/TopView.swift (1)
  • setTitleLabel (58-97)
DE/DE/Sources/Features/Search/ViewControllers/WineDetailViewController.swift (4)
DE/DE/Sources/Core/CommonUI/View/AverageTastingNoteView.swift (1)
  • configure (101-121)
DE/DE/Sources/Core/Extensions/UIViewController+Extensions.swift (1)
  • showToastMessage (18-31)
DE/DE/Sources/Features/TastingNote/Models/TNWineDataManager.swift (1)
  • updateWineData (40-58)
DE/DE/Sources/Network/WishList/Service/WishlistService.swift (2)
  • postWishlist (40-44)
  • deleteWishlist (50-54)
DE/DE/Sources/Features/TastingNote/ViewControllers/CreateVCs/NoseTestVC.swift (1)
DE/DE/Sources/Features/TastingNote/Views/ViewComponents/NoseEditTopView.swift (1)
  • setTitleLabel (92-131)
DE/DE/Sources/Core/CommonUI/View/AverageTastingNoteView.swift (3)
DE/DE/Sources/Core/Extensions/UIView+Extensions.swift (1)
  • addSubviews (78-80)
DE/DE/Sources/DesignSystem/Font/TextStyle.swift (1)
  • apply (68-70)
DE/DE/Sources/Core/Model/WineReviewModel.swift (5)
  • sugarContentDescription (76-85)
  • acidityDescription (87-96)
  • tanninDescription (98-107)
  • bodyDescription (109-118)
  • alcoholDescription (120-129)
🪛 SwiftLint (0.57.0)
DE/DE/Sources/Features/TastingNote/ViewControllers/Common/TNWineDetailViewController.swift

[Warning] 19-19: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

DE/DE/Sources/Features/Search/ViewControllers/WineDetailViewController.swift

[Warning] 19-19: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

🔇 Additional comments (4)
DE/DE/Sources/Network/TastingNote/Service/TastingNoteService.swift (1)

29-31: makePostNoteDTO 호출부 점검 완료
모든 호출부에 vintage 인자가 정상 반영되었습니다. 서버 스키마에서 vintageYear=0 허용 여부를 백엔드에 확인해 주세요.

DE/DE/Sources/Features/Search/ViewControllers/WineDetailViewController.swift (3)

99-118: 잘 구현된 빈티지 선택 플로우!

빈티지 선택 화면과의 통합이 깔끔하게 구현되었습니다. 특히:

  • 콜백을 통한 데이터 전달이 적절함
  • 선택된 빈티지가 변경된 경우에만 API를 재호출하는 최적화가 좋음
  • UI 업데이트와 데이터 갱신이 잘 분리되어 있음

334-337: 빈티지 정보 처리 로직 확인 필요

API 응답에서 vintageYear를 받아 저장하고 있는데, 이미 사용자가 선택한 vintage 값이 있을 수 있습니다. 두 값이 충돌할 경우 어떤 값을 우선시할지 명확히 해야 합니다.

현재 로직이 맞는지 확인해 보세요:

  1. 사용자가 빈티지를 선택한 후 API 호출 시 해당 빈티지로 요청
  2. API 응답의 vintageYear가 요청한 값과 다를 수 있는지?
  3. 만약 다르다면 어떤 값을 표시해야 하는지?
 if let vintageYear = wineResponse.vintageYear {
+    // API 응답의 빈티지가 이미 선택된 값과 다른 경우 처리 필요
+    // self.vintage와 vintageYear 비교 로직 추가 고려
     self.vintage = vintageYear
 }

432-435: 좋아요 API 호출 시 빈티지 정보 전달 확인

위시리스트 추가/삭제 시 vintage 정보를 함께 전달하도록 수정된 것이 좋습니다. 빈티지별로 위시리스트를 관리할 수 있게 되었네요.

Also applies to: 446-449

@doyeonk429 doyeonk429 merged commit 5cbac35 into develop Sep 4, 2025
1 check passed
@doyeonk429 doyeonk429 deleted the feat/issue-178 branch September 4, 2025 14:38
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] 와인 검색 과정에서 빈티지 Flow 추가

3 participants