-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat-T3-201] 제보 히스토리 화면 구현 및 화면 연결 (완료) #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough보고서 관련 뷰모델 및 뷰컨트롤러의 이름을 변경하여 역할을 명확히 하였습니다: ReportViewModel → ReportRegistrationViewModel, ReportListHistoryViewModel → ReportHistoryViewModel. 보고서 진행 상태 표시 및 빈 상태 표시 UI 컴포넌트를 추가하고, 카테고리 필터링 기능을 구현했습니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Projects/Presentation/Sources/Report/ViewModel/ReportHistoryViewModel.swift (1)
68-83: 제보 리스트 로딩/필터링 로직이 비어 있어 실제로 동작하지 않습니다현재 구현상 몇 가지 문제가 있습니다.
filterCategory/filterProgress에서 매번filterReports()를 호출하지만,filterReports()가 비어 있어 필터가 전혀 적용되지 않습니다.fetchReports()도reportSubject.send(ReportHistoryItem.dummyData)호출이 주석 처리되어 있어, 어느 시점에도reportSubject나reports배열이 채워지지 않습니다.- 결과적으로
reportsPublisher는 항상 빈 배열만 방출하고, 화면은 항상 비어 있는 상태(또는 emptyView만)로 남게 됩니다.실제 기능을 사용 가능하게 하려면 최소한 다음 정도는 필요해 보입니다.
- private func filterReports() { - - } - - private func fetchReports() { - //reportSubject.send(ReportHistoryItem.dummyData) - } + private func filterReports() { + let allReports = reports + + let selectedProgresses = progressSubject.value + .filter { $0.isSelected } + .map(\.progress) + + let filtered = allReports.filter { item in + let matchesCategory: Bool + if let category = selectedReportCategory { + matchesCategory = item.type == category + } else { + matchesCategory = true + } + + let matchesProgress: Bool + if selectedProgresses.isEmpty { + matchesProgress = true + } else { + matchesProgress = selectedProgresses.contains(item.progress) + } + + return matchesCategory && matchesProgress + } + + reportSubject.send(filtered) + } + + private func fetchReports() { + // TODO: 실제 API 연동 시 reports 에 원본 리스트를 채우고 filterReports() 를 호출하도록 수정합니다. + reports = ReportHistoryItem.dummyData + filterReports() + }위와 같이라도 임시로 동작을 붙여 두면, 이후 도메인 레이어 연동 시
fetchReports()부분만 실제 use case 호출로 교체하면 될 것 같습니다.Also applies to: 101-107
🧹 Nitpick comments (8)
Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (1)
59-73:reportHistory선택 시 ViewModel에서 별도 처리 없음현재
.reportHistory분기는break만 있어서 ViewModel 레벨에서는 아무 작업도 하지 않습니다. 이 PR에서 내비게이션을MypageView쪽에서 직접 처리하고 있다면 문제는 없지만, 공지/FAQ처럼 공통 사이드이펙트(로그, 트래킹, Deeplink 등)를 ViewModel에 두고 싶다면 추후 이 케이스도 일관되게 처리하는 방향을 고려해 볼 수 있습니다.뷰 레이어에서
.reportHistory선택 시MypageViewModel.action을 중복 호출하고 있지는 않은지 한 번만 확인해 주세요.Projects/Presentation/Sources/Report/View/Component/Common/ReportProgressView.swift (1)
12-65:ReportProgressView구현 전반적으로 깔끔함
- 고정 높이 + 내부 라벨 인셋으로 캡슐형 뱃지를 잘 구현하셨고,
configure(with:)에서ReportProgress확장 프로퍼티를 사용하는 구조도 명확합니다.- 다만
configureLayout에서self.snp.makeConstraints로 높이를 내부에서 고정하는 패턴은, 나중에 이 뷰를 다른 높이로 재사용해야 할 때 유연성이 떨어질 수 있습니다. 그 경우에는intrinsicContentSize를 오버라이드하거나, 높이 제약은 상위 뷰에서만 관리하는 방향도 고려해 볼 수 있습니다.현재 ReportHistory 셀 사용처 기준으로는 문제 없어 보입니다.
Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryEmptyView.swift (1)
11-68: 빈 상태 뷰 구현은 무난하지만stackViewSpacing활용을 제안
- 배경색, 폰트, 카피 모두 제보 히스토리 빈 상태를 잘 설명하고 있고, 뷰 컨트롤러에서 중앙 정렬하는 제약과도 자연스럽게 맞습니다.
Layout.stackViewSpacing상수를 정의해 두셨는데, 실제로는labelStackView.spacing에 적용되지 않아 두 라벨이 거의 붙어 보일 수 있습니다.다음처럼 spacing을 적용하면 의도한 여백을 쉽게 조절할 수 있을 것 같습니다:
private func configureAttribute() { backgroundColor = BitnagilColor.gray99 labelStackView.axis = .vertical labelStackView.alignment = .center + labelStackView.spacing = Layout.stackViewSpacing기능에는 영향 없고 UI 미세 개선 정도라, 여유 있을 때 반영해도 될 것 같습니다.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (1)
162-185: 마이페이지 메뉴 분기 구조는 좋고, DI 사용 표기만 통일하면 더 깔끔할 것 같습니다
switch selectedMenu로 각 메뉴의 네비게이션을 분리한 구조는 읽기 쉽고, 케이스 추가에도 안전해서 좋습니다.- 다만 같은 파일에서
settingButtonTapped는Shared.DIContainer.shared를, 새 코드는DIContainer.shared를 사용하고 있어 표기가 섞여 있습니다. 한 쪽 스타일로 통일하면 나중에 검색/리팩터링 시 혼란이 줄어들 것 같습니다.기능적으로는 문제 없어 보입니다.
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
124-129: Report 관련 ViewModel DI 등록은 rename 과 잘 맞습니다
ReportRegistrationViewModel(reportUseCase: ...)등록이 도메인 의존성과 잘 연결되어 있고,ReportHistoryViewModel도 현재 설계(의존성 없음)와 일치하게 파라미터 없는 생성자를 사용하고 있어서, 전체 DI 구성이 일관적입니다.자잘한 부분으로는,
ReportHistoryViewModel등록 클로저에서container인자를 사용하지 않으므로 아래처럼_로 바꾸면 경고를 줄일 수 있습니다.- DIContainer.shared - .register(type: ReportHistoryViewModel.self) { container in - return ReportHistoryViewModel() - } + DIContainer.shared + .register(type: ReportHistoryViewModel.self) { _ in + ReportHistoryViewModel() + }Also applies to: 131-134
Projects/Presentation/Sources/Report/View/ReportRegistrationViewController.swift (1)
1-5: 등록 화면 네이밍/타입 변경은 일관성 있게 잘 정리되었습니다
- 클래스명과 BaseViewController 제네릭을
ReportRegistrationViewController/ReportRegistrationViewModel로 맞춘 덕분에 “제보 등록” 역할이 분명해졌고,- 관련된 모든 extension 수신 타입도 새 이름으로 잘 따라가 있어서 런타임 이슈는 없을 것으로 보입니다.
한 가지 사소한 부분으로, 파일 헤더 주석의 파일명이 아직
ReportViewController.swift로 남아 있는데, 실제 파일명과 맞춰 두면 추후 검색/탐색 시 혼동을 줄일 수 있을 것 같습니다.-// ReportViewController.swift +// ReportRegistrationViewController.swiftAlso applies to: 14-48, 454-549
Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swift (2)
157-163: 불필요한 구독/디버그 출력은 정리해 두는 편이 좋겠습니다
categoryPublisher구독에서reportTypes를 전혀 사용하지 않고 있어 현재로서는 의미 없는 구독입니다.applyProgressSnapshot안의print(items)는 남겨두면 콘솔 로그가 계속 쌓여서 디버깅 외에는 도움이 되지 않습니다.둘 다 다음처럼 정리해 두면 코드가 더 깔끔해질 것 같습니다.
- viewModel.output.categoryPublisher - .receive(on: DispatchQueue.main) - .sink(receiveValue: { reportTypes in - - }) - .store(in: &cancellables) + // TODO: 카테고리 목록을 UI 에서 사용할 계획이 없다면, categoryPublisher 구독은 제거해도 됩니다.- print(items) - progressDataSource?.apply(snapshot, animatingDifferences: true) + progressDataSource?.apply(snapshot, animatingDifferences: true)필요 시 추후에 실제 UI 요구사항이 생길 때 다시 구독을 추가해도 될 것 같습니다.
Also applies to: 261-267
292-300: 진행 상태 셀 선택 시 불필요한 캐스팅을 제거해 간단히 쓸 수 있습니다
NSDiffableDataSourceSnapshot<ProgressSection, ReportProgressItem>의itemIdentifiers는 이미[ReportProgressItem]타입이라, 아래 부분에서as? ReportProgressItem캐스팅은 항상 성공하는 불필요한 연산입니다.- guard - let snapshot = progressDataSource?.snapshot(), - let item = snapshot.itemIdentifiers[indexPath.item] as? ReportProgressItem - else { return } + guard + let snapshot = progressDataSource?.snapshot() + else { return } + + let item = snapshot.itemIdentifiers[indexPath.item]기능은 그대로 두면서 코드만 조금 더 단순해집니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(1 hunks)Projects/Presentation/Sources/MyPage/View/MypageView.swift(1 hunks)Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift(2 hunks)Projects/Presentation/Sources/Report/Model/ReportProgress+.swift(1 hunks)Projects/Presentation/Sources/Report/View/Component/Common/ReportProgressView.swift(1 hunks)Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryEmptyView.swift(1 hunks)Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryTableViewCell.swift(3 hunks)Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swift(9 hunks)Projects/Presentation/Sources/Report/View/ReportRegistrationViewController.swift(6 hunks)Projects/Presentation/Sources/Report/ViewModel/ReportHistoryViewModel.swift(4 hunks)Projects/Presentation/Sources/Report/ViewModel/ReportRegistrationViewModel.swift(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-11-19T13:49:08.326Z
Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 68
File: Projects/Presentation/Sources/Report/View/ReportCompleteViewController.swift:108-172
Timestamp: 2025-11-19T13:49:08.326Z
Learning: ReportCompleteViewController에서 backgroudView가 fomoImageView와 의도적으로 겹치도록 설계됨. 이를 위해 backgroudView의 top 제약을 fomoImageView.snp.top (또는 fomoImageView)을 기준으로 설정하여 오프셋을 적용함.
Applied to files:
Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryEmptyView.swiftProjects/Presentation/Sources/Report/View/Component/Common/ReportProgressView.swiftProjects/Presentation/Sources/Report/View/ReportHistoryViewController.swiftProjects/Presentation/Sources/Report/View/ReportRegistrationViewController.swiftProjects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryTableViewCell.swift
📚 Learning: 2025-08-17T13:30:29.342Z
Learnt from: taipaise
Repo: YAPP-Github/Bitnagil-iOS PR: 51
File: Projects/Presentation/Sources/Common/Component/RoutineCardView.swift:76-80
Timestamp: 2025-08-17T13:30:29.342Z
Learning: In SnapKit, even height constraints (intrinsic constraints) should be applied after adding the view to its superview hierarchy. While simple height constraints might work before adding to superview, it's not guaranteed and goes against best practices. The recommended approach is to call addArrangedSubview first, then apply constraints.
Applied to files:
Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swiftProjects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryTableViewCell.swift
📚 Learning: 2025-07-16T09:09:13.869Z
Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Applied to files:
Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryTableViewCell.swift
📚 Learning: 2025-07-16T09:21:15.038Z
Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Applied to files:
Projects/Presentation/Sources/MyPage/View/MypageView.swift
🧬 Code graph analysis (7)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (2)
Projects/Presentation/Sources/Report/ViewModel/ReportRegistrationViewModel.swift (1)
register(122-124)Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
register(14-16)resolve(18-25)
Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryEmptyView.swift (1)
Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swift (1)
configureLayout(82-155)
Projects/Presentation/Sources/Report/View/Component/Common/ReportProgressView.swift (1)
Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryTableViewCell.swift (3)
configureAttribute(47-69)configureLayout(71-157)configure(159-171)
Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swift (4)
Projects/Presentation/Sources/Report/ViewModel/ReportHistoryViewModel.swift (3)
action(55-66)fetchReports(105-107)filterCategory(68-83)Projects/Presentation/Sources/Report/View/ReportRegistrationViewController.swift (3)
configureAttribute(72-117)showCategoryBottomSheet(367-371)reportCategoryTableViewController(481-484)Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (2)
configureCustomNavigationBar(19-29)presentCustomBottomSheet(98-101)Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryEmptyView.swift (1)
configureAttribute(35-48)
Projects/Presentation/Sources/Report/View/ReportRegistrationViewController.swift (1)
Projects/Presentation/Sources/Report/ViewModel/ReportRegistrationViewModel.swift (2)
action(58-75)removePhoto(117-120)
Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryTableViewCell.swift (1)
Projects/Presentation/Sources/Report/View/Component/Common/ReportProgressView.swift (1)
configure(60-65)
Projects/Presentation/Sources/MyPage/View/MypageView.swift (2)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
resolve(18-25)Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (1)
action(52-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (7)
Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (1)
23-28:MypageMenu에reportHistory추가 LGTM마이페이지 메뉴에 "내 제보 기록" 항목을 추가한 부분은 네이밍/라벨링 모두 직관적이고 기존 패턴과도 잘 맞습니다.
Projects/Presentation/Sources/Report/ViewModel/ReportRegistrationViewModel.swift (1)
12-12:ReportRegistrationViewModel로의 타입명 변경 적절제보 등록 전용 ViewModel이라는 의도가 이름에서 잘 드러나고, 내부 인터페이스는 그대로라 기존 바인딩에도 영향이 없어 보입니다. DI 컨테이너/스토리보드(또는 생성부)까지 모두 이 이름으로 갱신되었다면 문제 없이 동작할 것 같습니다.
빌드 시점에 이전
ReportViewModel참조가 남아있지 않은지만 한 번만 체크해 주세요.Projects/Presentation/Sources/Report/Model/ReportProgress+.swift (1)
11-36:ReportProgress컬러 프로퍼티에서return누락으로 컴파일 에러 발생
backgroundColor/titleColor계산 프로퍼티의switch문 안에서 각 case에return이 없어 현재 상태로는 컴파일되지 않습니다.다음처럼 각 case에
return을 추가해야 합니다:extension ReportProgress { var backgroundColor: UIColor? { switch self { case .received: - BitnagilColor.green10 + return BitnagilColor.green10 case .inProgress: - BitnagilColor.skyblue10 + return BitnagilColor.skyblue10 case .completed: - BitnagilColor.gray95 + return BitnagilColor.gray95 case .entire: - nil + return nil } } var titleColor: UIColor? { switch self { case .received: - BitnagilColor.green500 + return BitnagilColor.green500 case .inProgress: - BitnagilColor.blue300 + return BitnagilColor.blue300 case .completed: - BitnagilColor.gray40 + return BitnagilColor.gray40 case .entire: - nil + return nil } } }Likely an incorrect or invalid review comment.
Projects/Presentation/Sources/Report/ViewModel/ReportHistoryViewModel.swift (1)
12-35: ViewModel 이름/프로퍼티 설계는 의도와 잘 맞습니다
ReportHistoryViewModel로의 rename과selectedReportCategory를private(set)으로 노출한 부분은 히스토리 화면 역할을 분리하고, VC에서 현재 선택된 카테고리를 조회할 수 있게 해줘서 구조가 깔끔합니다. 이 방향 자체는 그대로 가져가셔도 될 것 같습니다.Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryTableViewCell.swift (1)
27-29: 공통ReportProgressView사용으로 셀 역할이 더 명확해졌습니다히스토리 셀에서 상태 표시를 전용
ReportProgressView로 교체하고,configure(with: item.progress)한 줄로 위임한 구조가 깔끔합니다. 레이아웃도 기존 위치(top/leading)만 유지하면서 내부 구현은 컴포넌트에 맡겨서 유지보수에 유리해 보입니다.Also applies to: 71-75, 88-91, 159-161
Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swift (2)
31-34: 히스토리 empty 상태 처리와 레이아웃 구성이 자연스럽습니다
ReportHistoryEmptyView를 중앙에 고정된 크기로 두고,reportsPublisher에서historyEmptyView.isHidden = !reports.isEmpty로 토글하는 방식이 직관적입니다.
리스트가 비었을 때만 emptyView가 드러나고, 데이터가 있으면 바로 테이블 뷰가 보이도록 동작해서 UX 측면에서도 무리가 없어 보입니다.Also applies to: 45-46, 90-91, 148-154, 183-188
67-71: 카테고리 필터 bottom sheet 플로우가 ViewModel 과 잘 연결되어 있습니다
categoryButton에showCategoryBottomSheet()액션을 연결하고,- bottom sheet 생성 시
viewModel.selectedReportCategory를 넘겨 현재 선택값을 유지하며,ReportCategoryTableViewControllerDelegate에서 선택된ReportType을 받아viewModel.action(input: .filterCategory(type: ...))로 위임하는 흐름이 일관되고, 등록 화면(ReportRegistrationViewController)과도 패턴이 맞습니다.ViewModel 쪽
selectedCategoryPublisher바인딩으로 버튼 레이블이 갱신되므로 전체 데이터 흐름도 잘 맞춰져 있습니다.Also applies to: 136-146, 285-289, 321-326
choijungp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
띵푸루부 ~~~~~ 짱빠름 !!!!!!!!!!!!!!!!!
따봉 !!!!! 띵동 따봉 ~~ 👍🏻
| case .reportHistory: | ||
| break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사실 지금 크게 노메러이긴 하지만 .resetGoal과 케이스 합쳐두 될 것 같아유
| case .reportHistory: | |
| break | |
| case .resetGoal, .reportHistory: | |
| break |
글구 // 임시 url 주석도 이젠 없어두 될 것 같숨당 ~~
| private func configureLayout() { | ||
| addSubview(progressLabel) | ||
|
|
||
| self.snp.makeConstraints { make in | ||
| make.height.equalTo(Layout.progressViewHeight) | ||
| } | ||
|
|
||
| progressLabel.snp.makeConstraints { make in | ||
| make.height.equalTo(Layout.progressLabelHeight) | ||
|
|
||
| make.verticalEdges | ||
| .equalToSuperview() | ||
| .inset(Layout.progressLabelVerticalSpacing) | ||
|
|
||
| make.horizontalEdges | ||
| .equalToSuperview() | ||
| .inset(Layout.progressLabelHorizontalSpacing) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReportProgressView !!!!!!!! 나이쓰 따봉 ~~~
일케 하면 나중에 ReportProgressView 사용하는 곳에서 ReportProgressView의 레이아웃 사이즈 따로 안정해줘도 ReportProgress에 맞게 저절로 정해지나유 ??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일단은 그렇게 해두긴했는데, 호옥시 안되면 말씀해주세요!!
📱 Screenshot
👩💻 Contents
Summary by CodeRabbit
릴리스 노트
✏️ Tip: You can customize this high-level summary in your review settings.