⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

Conversation

@taipaise
Copy link
Collaborator

@taipaise taipaise commented Nov 19, 2025

🌁 Background

📱 Screenshot

iPhone SE3 iPhone 13 mini iPhone 16 Pro
se3_history 13mini 16pro

👩‍💻 Contents

  • 제보 엔티티 정의
  • 제보 히스토리 화면 구현 (1차)

📝 Review Note

  • 내일 조금만 더 작업하면 진짜 완성할 수 있을것 같아요~ 킵 고잉~!~!
  • 그 진행사항 표시하는 label이 일단은 폰트 설정을 잘 해두긴했는데,, 좀 어색해보여요.. 다른게 급해서 자세히 확인해보지는 않았는데 이슈 있으면 다시 말씀드리겠습니다.

더미데이터

빠른 확인을 위해 ReportHistoryItem 에 더미를 생성할 수 있는 코드를 지피티로 뚝딱했습니다. 내일 UI 완성 시 삭제하도록 하겠습니다!

Summary by CodeRabbit

새로운 기능

  • 보고서 이력 조회 기능 추가: 진행상태(제보 완료, 처리 중, 처리 완료)와 카테고리별 필터링 가능
  • 보고서 목록에 진행상태 배지 및 썸네일 이미지 표시
  • 날짜별로 정렬된 보고서 이력 조회

개선 사항

  • 보고서 위치 정보 처리 개선 (선택적 좌표 지원)
  • 신고 유형별 아이콘 시각화 개선

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

@taipaise taipaise requested a review from choijungp November 19, 2025 14:16
@taipaise taipaise self-assigned this Nov 19, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 19, 2025

Walkthrough

보고 이력 조회 기능을 추가하여 보고서 목록을 날짜별로 표시하고 진행 상태 및 카테고리로 필터링할 수 있도록 구현했습니다. 도메인 계층의 LocationEntity와 ReportEntity를 확장하고 새로운 UI 컴포넌트 및 ViewModel을 추가했습니다.

Changes

Cohort / File(s) 변경 요약
Domain 계층 - 위치 및 보고서 엔티티
Projects/Domain/Sources/Entity/LocationEntity.swift, Projects/Domain/Sources/Entity/LocationRepository.swift
LocationEntity의 longitude, latitude를 Double에서 Double?로 변경하여 선택적으로 만들었고, LocationRepository.fetchAddress에서 unwrap guard를 추가하여 nil 체크
Domain 계층 - 새로운 enum 및 엔티티
Projects/Domain/Sources/Entity/Enum/ReportProgress.swift, Projects/Domain/Sources/Entity/ReportEntity.swift
ReportProgress enum (entire, received, inProgress, completed) 추가 및 한글 description 제공; ReportEntity에 id, title, date, type, progress, content, location, photoUrls 프로퍼티 및 initializer 추가
Presentation 계층 - 모델
Projects/Presentation/Sources/Report/Model/PhotoItem.swift, Projects/Presentation/Sources/Report/Model/ReportHistoryItem.swift, Projects/Presentation/Sources/Report/Model/ReportProgressItem.swift
PhotoItem을 public에서 internal로 가시성 변경; ReportHistoryItem (id, title, thumbnailUrl, date, type, progress, location) 신규 추가 및 dummyData 제공; ReportProgressItem (uuid, progress, count, isSelected) 신규 추가
Presentation 계층 - UI 컴포넌트
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift, Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift, Projects/Presentation/Sources/Report/View/Component/ReportProgressCollectionViewCell.swift
ReportHistoryTableHeaderView (날짜 및 요일 레이블) 신규 추가; ReportHistoryTableViewCell (보고서 아이템 표시 및 진행 상태 색상 테마) 신규 추가; ReportProgressCollectionViewCell (진행 상태 필터 표시) 신규 추가 및 ReportProgress extension으로 backgroundColor, titleColor 제공
Presentation 계층 - ViewController 및 ViewModel
Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift, Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift
ReportListHistoryViewController 신규 추가 (진행 상태 collection view 및 날짜별 history table view, 필터링 기능); ReportListHistoryViewModel 신규 추가 (Input/Output 타입, 진행 상태/카테고리 필터링 로직)
Presentation 계층 - 기타 업데이트
Projects/Presentation/Sources/Report/View/Component/ReportCategoryTableViewCell.swift, Projects/Presentation/Sources/Report/View/Component/ReportPhotoCollectionViewCell.swift, Projects/Presentation/Sources/Report/View/ReportViewController.swift, Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
ReportCategoryTableViewCell의 icon 매핑 변경 (transportation: carIcon, lamp: lightIcon, water: waterIcon, convenience: hammerIcon); ReportPhotoCollectionViewCell의 configure 메서드 파라미터 레이블을 item에서 with로 변경; ReportViewController 업데이트; PresentationDependencyAssembler에 ReportListHistoryViewModel DI 등록

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant RLHViewController as ReportListHistoryViewController
    participant RLHViewModel as ReportListHistoryViewModel
    participant Domain as Domain Layer

    User ->> RLHViewController: viewDidLoad
    RLHViewController ->> RLHViewModel: action(.fetchReports)
    RLHViewModel ->> Domain: 더미 데이터 로드
    RLHViewModel -->> RLHViewController: reportsPublisher 업데이트
    RLHViewController ->> RLHViewController: Table view snapshot 적용

    User ->> RLHViewController: 진행 상태 필터 선택
    RLHViewController ->> RLHViewModel: action(.filterProgress)
    RLHViewModel ->> RLHViewModel: 선택 상태 토글
    RLHViewModel -->> RLHViewController: progressPublisher 업데이트
    RLHViewController ->> RLHViewController: Collection view snapshot 적용

    User ->> RLHViewController: 카테고리 필터 선택
    RLHViewController ->> RLHViewModel: action(.filterCategory)
    RLHViewModel ->> RLHViewModel: 선택된 카테고리 변경
    RLHViewModel -->> RLHViewController: selectedCategoryPublisher 업데이트
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

  • ReportListHistoryViewController: 복합적인 UI 구성 (collection view + table view), 다중 diffable data source 관리, Combine 바인딩 로직 검증 필요
  • ReportListHistoryViewModel: Input/Output 패턴 구현, 필터링 로직, 상태 관리 (CurrentValueSubject/PassthroughSubject) 정확성 확인 필요
  • 새로운 UI 컴포넌트들: ReportHistoryTableHeaderView, ReportHistoryTableViewCell, ReportProgressCollectionViewCell의 layout 및 data binding 정확성
  • Domain 엔티티 변경: LocationEntity의 optional 변경이 기존 코드 전방에 영향을 미칠 수 있는지 확인 필요
  • 가시성 변경: PhotoItem을 public에서 internal로 변경한 것이 다른 모듈에서 사용되는지 확인 필요

Possibly related PRs

Suggested reviewers

  • choijungp

Poem

🐰 보고서의 역사를 기록하고,
필터로 걸러 정리하며,
날짜별 바구니에 담아내네!
새로운 기능, 반짝반짝✨
History of reports, sorted with care! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive PR 제목 'Feat/report'는 매우 모호하고 일반적이며, 실제 변경 사항의 구체적 내용을 명확히 전달하지 못합니다. 제목을 더 구체적으로 수정하세요. 예: 'Add Report History screen and ReportEntity' 또는 'Implement Report History feature with entity definitions'와 같이 주요 변경 사항을 명시적으로 표현하는 것이 좋습니다.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/report

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (8)
Projects/DataSource/Sources/Repository/LocationRepository.swift (1)

39-51: 좌표 nil 방어 로직 추가 방향 👍 / fallback 인자 전달 방식 소소한 개선 제안

fetchAddress(coordinate:) 에서 위경도가 없는 경우 조기에 nil 을 반환하도록 guard let 을 추가한 것은, 잘못된 엔드포인트 호출을 막는다는 점에서 좋은 변경입니다.

guard
    let longitude = coordinate.longitude,
    let latitude = coordinate.latitude
else { return nil }

let endpoint = LocationEndpoint.fetchAddress(longitude: longitude, latitude: latitude)

추가로, toLocationEntity 호출 시 fallback 으로 현재는 옵셔널 프로퍼티(coordinate.longitude, coordinate.latitude)를 그대로 넘기고 있는데, 이 함수 시그니처가 Double 기반이라면 언랩된 longitude, latitude 를 넘기는 편이 타입/의도 면에서 더 자연스러울 수 있습니다. 시그니처가 이미 Double? 라면 현재 코드도 문제는 없습니다.

Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)

131-134: ReportListHistoryViewModel 등록 시 미사용 파라미터 정리 (SwiftLint 경고 대응)

새 DI 등록 자체는 자연스러운데, 클로저 파라미터 container 를 사용하지 않아 SwiftLint 의 unused_closure_parameter 경고가 발생하고 있습니다. 지금은 의존성이 없는 뷰모델이므로, 아래처럼 파라미터를 _ 로 바꿔 주면 경고도 사라지고 의도도 더 명확해집니다.

-        DIContainer.shared
-            .register(type: ReportListHistoryViewModel.self) { container in
-            return ReportListHistoryViewModel()
-        }
+        DIContainer.shared
+            .register(type: ReportListHistoryViewModel.self) { _ in
+                ReportListHistoryViewModel()
+            }

나중에 ReportListHistoryViewModel 에 의존성이 추가되면 그때 _ 를 실제 이름으로 되돌리고 container.resolve 를 사용하시면 될 것 같습니다.

Projects/Presentation/Sources/Report/View/Component/ReportProgressCollectionViewCell.swift (1)

56-69: 셀 구성 로직이 잘 구현되었습니다.

진행 상태와 카운트를 조건부로 표시하는 로직이 명확하며, 선택 상태에 따른 스타일 변경도 적절합니다.

선택적 개선사항: Line 59의 item.count != 0item.count > 0로 변경하면 의도가 더 명확해질 수 있습니다(음수 방지).

Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift (5)

12-30: 레이아웃 상수 네이밍 및 safe area 하단 제약 확인 필요

  • Layout.cetegoryButtonHeight 오타는 기능에는 문제 없지만, 추후 유지보수성을 위해 categoryButtonHeight 등으로 정리하는 게 좋습니다.
  • historyTableView 의 bottom 제약이 safeAreaLayoutGuide 가 아니라 superview 에 바로 붙어 있어서, 홈 인디케이터 영역과 겹칠 수 있습니다. 의도라면 괜찮지만, 다른 화면들과 일관성 있게 safeArea 기준으로 맞출지 한 번 확인 부탁드립니다.

Also applies to: 92-103, 124-134


55-69: 카테고리 UI 및 선택/상세 선택 바인딩이 아직 미완성 상태로 보임

  • categoryButton 에 대한 addTarget 이 없어 현재는 탭해도 아무 동작을 하지 않습니다.
  • categoryPublisher 구독 클로저(라인 140142)와 selectedReportPublisher 구독 클로저(라인 172174)가 비어 있어, 실제 카테고리 선택 UI 표시 및 제보 상세 화면 진입 로직이 아직 구현되지 않은 상태입니다.
  • PR 설명에 “내일 추가 작업 예정”이라고 되어 있으니, 후속 커밋에서:
    • categoryButton 탭 → 카테고리 선택 UI 표시 (filterCategory 액션 호출)
    • selectedReportPublisher → 상세 화면 push/present
      를 연결해 주면 흐름이 완성될 것 같습니다.

Also applies to: 137-155, 170-175


71-135: 카테고리 버튼 오버레이 레이아웃은 의도대로인지 재확인 권장

  • categoryLabel/categoryButtonImage/categoryButtonhistoryTableView.top 을 기준으로 오버레이처럼 떠 있는 구조입니다. 스크롤 시 섹션 헤더와 겹치거나, 첫 섹션 셀과 시각적으로 부딪힐 수 있습니다.
  • 의도된 디자인(“테이블 상단에 고정된 필터 행”)이라면 괜찮지만, 다른 화면에서 유사한 UI 가 tableHeaderView 로 올라가는 패턴이라면 구조를 맞출지도 검토해 보셔도 좋겠습니다.

240-247: 디버그용 print(items) 는 제거 권장

  • applyProgressSnapshotprint(items)(라인 244)는 디버그 로그로 보이며, 나중에 실제 API 연동 시 콘솔 노이즈가 될 수 있습니다.
  • UI 검증이 끝나면 제거해 주세요.

265-273: 컬렉션 셀 선택 시 snapshot 인덱스 접근 방식 개선 제안

  • 현재 구현:
let snapshot = progressDataSource?.snapshot()
let item = snapshot.itemIdentifiers[indexPath.item] as? ReportProgressItem
  • itemIdentifiers 의 요소 타입이 이미 ReportProgressItem 이라 as? ReportProgressItem 캐스트는 항상 성공하는 불필요한 연산입니다.

  • snapshot 기반 인덱스 접근은 이론상 diffable snapshot 과 컬렉션 뷰의 인덱스가 어긋날 여지가 있습니다.

  • 좀 더 안전하고 간결하게는 itemIdentifier(for:) 를 직접 사용하는 것이 좋습니다.

예시:

guard let item = progressDataSource?.itemIdentifier(for: indexPath) else { return }
viewModel.action(input: .filterProgress(progress: item.progress))
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7740ce and 63e7c59.

📒 Files selected for processing (18)
  • Projects/DataSource/Sources/Repository/LocationRepository.swift (1 hunks)
  • Projects/Domain/Sources/Entity/Enum/ReportProgress.swift (1 hunks)
  • Projects/Domain/Sources/Entity/LocationEntity.swift (1 hunks)
  • Projects/Domain/Sources/Entity/ReportEntity.swift (1 hunks)
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1 hunks)
  • Projects/Presentation/Sources/Report/Model/PhotoItem.swift (1 hunks)
  • Projects/Presentation/Sources/Report/Model/ReportHistoryItem.swift (1 hunks)
  • Projects/Presentation/Sources/Report/Model/ReportProgressItem.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/Component/ReportCategoryTableViewCell.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/Component/ReportHistoryCategoryCollectionViewCell.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/Component/ReportPhotoCollectionViewCell.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/Component/ReportProgressCollectionViewCell.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/ReportHistoryListViewController.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/ReportViewController.swift (1 hunks)
  • Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-19T13:49:08.315Z
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.315Z
Learning: ReportCompleteViewController에서 backgroudView가 fomoImageView와 의도적으로 겹치도록 설계됨. 이를 위해 backgroudView의 top 제약을 fomoImageView.snp.top (또는 fomoImageView)을 기준으로 설정하여 오프셋을 적용함.

Applied to files:

  • Projects/Presentation/Sources/Report/View/ReportHistoryListViewController.swift
  • Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift
  • Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift
🧬 Code graph analysis (8)
Projects/Presentation/Sources/Report/View/ReportHistoryListViewController.swift (2)
Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift (3)
  • configureAttribute (55-69)
  • configureLayout (71-135)
  • bind (137-176)
Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)
  • configureCustomNavigationBar (19-29)
Projects/Presentation/Sources/Report/View/ReportViewController.swift (1)
Projects/Presentation/Sources/Report/View/Component/ReportPhotoCollectionViewCell.swift (1)
  • configure (85-88)
Projects/Presentation/Sources/Report/View/Component/ReportPhotoCollectionViewCell.swift (3)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift (1)
  • configure (62-68)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift (1)
  • configure (182-201)
Projects/Presentation/Sources/Report/View/Component/ReportProgressCollectionViewCell.swift (1)
  • configure (56-69)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
  • register (14-16)
Projects/Presentation/Sources/Report/View/Component/ReportProgressCollectionViewCell.swift (1)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryCategoryCollectionViewCell.swift (3)
  • configureAttribute (32-40)
  • configureLayout (42-54)
  • configure (56-73)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryCategoryCollectionViewCell.swift (1)
Projects/Presentation/Sources/Report/View/Component/ReportProgressCollectionViewCell.swift (3)
  • configureAttribute (32-40)
  • configureLayout (42-54)
  • configure (56-69)
Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift (6)
Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift (3)
  • action (54-65)
  • fetchReports (103-105)
  • filterProgress (83-97)
Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)
  • configureCustomNavigationBar (19-29)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift (1)
  • configure (62-68)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift (1)
  • configure (182-201)
Projects/Presentation/Sources/Report/View/Component/ReportProgressCollectionViewCell.swift (1)
  • configure (56-69)
Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1)
  • chevronIcon (49-51)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift (2)
Projects/Presentation/Sources/Report/View/Component/ReportCategoryTableViewCell.swift (1)
  • configureAttribute (45-55)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift (2)
  • configureAttribute (53-78)
  • configure (182-201)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Report/View/ReportHistoryListViewController.swift

[Warning] 110-110: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 117-117: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 124-124: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 131-131: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 138-138: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 145-145: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift

[Warning] 132-132: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

⏰ 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 (12)
Projects/Domain/Sources/Entity/LocationEntity.swift (1)

9-15: 위경도 옵셔널 변경 방향 적절

longitude, latitudeDouble? 로 바꾼 덕분에 좌표가 없는 위치도 표현할 수 있어 도메인 유연성이 좋아졌습니다. 아래처럼 Repository 에서도 nil 가드가 들어가 있어 일관성도 맞습니다.

  • LocationRepository.fetchAddress(coordinate:) 에서 guard let 으로 언랩 후 사용

추가로, 다른 사용처에서 강제 언랩(!) 이 남아 있지 않은지만 한 번만 확인해 주시면 충분할 것 같습니다.

Projects/Presentation/Sources/Report/View/Component/ReportHistoryCategoryCollectionViewCell.swift (1)

12-73: 카테고리 셀 구성 깔끔하고 기존 패턴과 일관

레이아웃/폰트/코너 라운딩 설정이 기존 ReportProgressCollectionViewCell 과 잘 맞고, count 가 있을 때만 공백을 붙여 표현하는 부분도 명확합니다. 선택 상태에 따른 배경/텍스트 컬러 토글도 직관적이라 재사용성과 가독성이 좋습니다.

현재 형태 그대로 사용하셔도 무방해 보입니다.

Projects/Presentation/Sources/Report/Model/PhotoItem.swift (1)

10-17: PhotoItem 가시성 축소로 프레젠테이션 모듈 캡슐화 개선

PhotoItempublic → 모듈 내부용으로 줄이고, 프로퍼티/이니셜라이저도 동일하게 internal 로 맞춘 것은 Presentation 모듈의 API 표면을 줄인다는 점에서 좋은 방향입니다. Hashable 유지로 diffable datasource 등에서도 그대로 활용 가능해 보입니다.

외부 모듈에서 직접 PhotoItem 을 참조하지 않는 구조라면 현재 변경은 문제 없이 적절해 보입니다.

Projects/Presentation/Sources/Report/View/Component/ReportCategoryTableViewCell.swift (1)

108-115: 아이콘 매핑 개선이 적절합니다.

각 리포트 타입에 대한 아이콘 매핑이 더 명확하고 의미있는 아이콘으로 업데이트되었습니다.

Projects/Presentation/Sources/Report/View/Component/ReportPhotoCollectionViewCell.swift (1)

85-88: 메서드 시그니처 개선이 좋습니다.

configure(with:) 형태로 변경하여 Swift API 디자인 가이드라인과 일관성을 유지하고 있으며, 다른 셀 컴포넌트들과의 일관성도 확보되었습니다.

Projects/Presentation/Sources/Report/View/ReportViewController.swift (1)

373-377: 셀 구성 호출이 올바르게 업데이트되었습니다.

ReportPhotoCollectionViewCell의 새로운 메서드 시그니처와 일치하도록 정확하게 업데이트되었습니다.

Projects/Presentation/Sources/Report/Model/ReportProgressItem.swift (1)

11-16: 모델 정의가 적절합니다.

선택 상태를 추적하기 위한 isSelectedvar로, 나머지 불변 속성들을 let으로 선언하여 적절한 가변성 설계를 유지하고 있습니다.

Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift (2)

182-201: 히스토리 아이템 구성 로직이 잘 구현되었습니다.

옵셔널 처리와 이미지 로딩이 적절하게 구현되어 있으며, Kingfisher를 사용한 이미지 로딩도 올바릅니다.


204-230: ReportProgress 확장이 적절합니다.

Domain 타입에 대한 Presentation 레이어의 확장을 통해 UI 관련 색상을 제공하는 것은 이 아키텍처에서 수용 가능한 패턴입니다. 각 진행 상태별로 적절한 색상이 매핑되어 있습니다.

Projects/Presentation/Sources/Report/View/ReportHistoryListViewController.swift (1)

41-43: configureAttribute 구현이 불완전합니다.

configureAttribute() 메서드가 categoryButton.backgroundColor 설정만 포함하고 있습니다. 파일의 다른 유사한 뷰 컨트롤러(ReportListHistoryViewController.swift)를 보면, 이 메서드에서 폰트, 색상, 컬렉션뷰/테이블뷰 설정 등 더 많은 속성 구성이 필요합니다.

PR 설명에 언급된 대로 추가 작업이 예정되어 있으므로, 다음 구성들을 추가해주세요:

  • categoryLabel, categoryButtonImage 스타일 설정
  • progressCollectionView 레이아웃 및 데이터소스 구성
  • historyTableView 스타일 및 데이터소스 구성
  • view.backgroundColor 설정

완전한 구현 예시가 필요하시면 도움을 드릴 수 있습니다.

⛔ Skipped due to learnings
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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift (1)

178-213: Diffable Data Source + Compositional Layout 사용 구조는 전체적으로 깔끔함

  • UICollectionViewDiffableDataSourceUITableViewDiffableDataSource 를 각각 분리해서 구성하고, 레이아웃/등록/델리게이트 설정을 configure* 메서드로 나눈 구조가 읽기 쉽고 확장하기 좋아 보입니다.
  • 프로그레스 컬렉션의 estimated 폭, interItemSpacing, continuous 스크롤 조합도 태그형 필터 UI에 적절해 보입니다.

Also applies to: 215-238

Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift (1)

12-26: Input/Output 구조 및 Combine 기반 설계는 전체적으로 좋음

  • Input/Output 으로 입출력을 명확히 분리하고, 내부는 CurrentValueSubject/PassthroughSubject 로 감추는 패턴이 깔끔합니다.
  • ReportProgress.allCasesReportProgressItem 초기화, ReportType.allCases 초기 세팅도 명확해서 이후 실제 API 연동 시 교체하기 쉬울 것 같습니다.

Also applies to: 28-52

Comment on lines +21 to +117
static var dummyData: [ReportHistoryItem] {
let types = ReportType.allCases
let progresses = ReportProgress.allCases.filter { $0 != .entire }

func makeItem(
id: Int,
title: String,
date: String,
location: String,
typeIndex: Int,
progressIndex: Int
) -> ReportHistoryItem {
let type = types[typeIndex % types.count]
let progress = progresses[progressIndex % progresses.count]

return ReportHistoryItem(
id: id,
title: title,
thumbnailUrl: "https://picsum.photos/200",
date: date,
type: type,
progress: progress,
location: location
)
}

return [
makeItem(
id: 1,
title: "횡단보도 앞 가로등 불이 꺼져 있어요",
date: "2025-11-19월",
location: "서울시 강남구 삼성동",
typeIndex: 0,
progressIndex: 0
),
makeItem(
id: 2,
title: "지하철 역사 계단 난간이 파손되었어요",
date: "2025-11-19월",
location: "서울시 송파구 잠실동",
typeIndex: 1,
progressIndex: 1
),

// 다른 날짜
makeItem(
id: 3,
title: "공원 놀이터 바닥이 파여 있어 위험해요",
date: "2025-11-18월",
location: "서울시 마포구 상암동",
typeIndex: 2,
progressIndex: 2
),
makeItem(
id: 4,
title: "버스 정류장 안내판 조명이 나갔습니다",
date: "2025-11-18월",
location: "서울시 서초구 서초동",
typeIndex: 3,
progressIndex: 0
),

makeItem(
id: 5,
title: "자전거 도로에 불법 주차된 차량이 있어요",
date: "2025-11-17월",
location: "서울시 노원구 공릉동",
typeIndex: 1,
progressIndex: 1
),
makeItem(
id: 6,
title: "보도블럭이 들떠서 걸려 넘어질 위험이 있어요",
date: "2025-11-16월",
location: "서울시 종로구 종로1가",
typeIndex: 0,
progressIndex: 2
),

makeItem(
id: 7,
title: "교차로 신호등이 고장난 것 같습니다",
date: "2025-11-16월",
location: "서울시 동작구 사당동",
typeIndex: 2,
progressIndex: 0
),
makeItem(
id: 8,
title: "지하차도에 물이 고여 있어요",
date: "2025-11-15월",
location: "서울시 영등포구 영등포동",
typeIndex: 3,
progressIndex: 1
)
]
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

더미 데이터의 날짜 형식을 확인해주세요.

더미 데이터의 모든 날짜가 "월"(월요일) 요일 접미사를 가지고 있습니다. 예를 들어 2025-11-19, 2025-11-18, 2025-11-17 등 서로 다른 날짜임에도 모두 "월"로 표시되어 있어 실제 요일과 일치하지 않을 수 있습니다. UI 검증을 위해 다양한 요일 표현(월, 화, 수 등)을 사용하는 것이 좋겠습니다.

참고: PR 설명에 언급된 대로, UI 완성 시 이 더미 데이터는 제거 예정입니다.

날짜별로 올바른 요일을 계산하는 헬퍼 함수를 생성하는 것이 도움이 될까요?

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Report/Model/ReportHistoryItem.swift around
lines 21 to 117, the dummy items use hard-coded weekday suffixes ("월") that may
not match the actual weekday for each date; replace these hard-coded weekday
strings by computing the correct weekday from the date value: add a small helper
that parses the date string (use the same format used elsewhere, e.g.
"yyyy-MM-dd"), computes the calendar weekday, maps it to the Korean weekday
symbols ("일","월","화","수","목","금","토"), and returns a formatted date string like
"2025-11-19월"; update the dummyData builder to call this helper for each item
(or store correctly formatted date strings) and make the helper resilient to
parse failures by falling back to the original string or a safe default.

Comment on lines 96 to 104
categoryButton.snp.makeConstraints { make in
make.centerY.equalTo(categoryButtonImage)

make.trailing
.equalToSuperview()
.offset(-Layout.horizontalSpacing)

make.height.equalTo(Layout.cetegoryButtonHeight)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

categoryButton의 레이아웃 제약이 불완전합니다.

categoryButton의 제약 조건이 불완전합니다. leading 또는 width 제약이 없어 버튼의 가로 크기가 결정되지 않습니다. ReportListHistoryViewController.swift의 구현을 참고하면, leadingcategoryLabel.snp.leading에 연결해야 합니다.

다음과 같이 수정해주세요:

 categoryButton.snp.makeConstraints { make in
     make.centerY.equalTo(categoryButtonImage)
+    
+    make.leading.equalTo(categoryLabel.snp.leading)
     
     make.trailing
         .equalToSuperview()
         .offset(-Layout.horizontalSpacing)
     
     make.height.equalTo(Layout.cetegoryButtonHeight)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
categoryButton.snp.makeConstraints { make in
make.centerY.equalTo(categoryButtonImage)
make.trailing
.equalToSuperview()
.offset(-Layout.horizontalSpacing)
make.height.equalTo(Layout.cetegoryButtonHeight)
}
categoryButton.snp.makeConstraints { make in
make.centerY.equalTo(categoryButtonImage)
make.leading.equalTo(categoryLabel.snp.leading)
make.trailing
.equalToSuperview()
.offset(-Layout.horizontalSpacing)
make.height.equalTo(Layout.cetegoryButtonHeight)
}
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Report/View/ReportHistoryListViewController.swift
around lines 96 to 104, the categoryButton constraints are incomplete because
its horizontal size is not defined; add a leading constraint to align
categoryButton.leading to categoryLabel.snp.leading (or alternatively add an
explicit width) so the button’s horizontal size is resolved, keeping the
existing centerY, trailing, and height constraints.

Comment on lines 107 to 149
override func bind() {
viewModel.output.categoryPublisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { reportTypes in

})
.store(in: &cancellables)

viewModel.output.selectedCategoryPublisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { selectedCategory in

})
.store(in: &cancellables)

viewModel.output.progressPublisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { progresses in

})
.store(in: &cancellables)

viewModel.output.selectedProgressPublisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { selectedProgress in

})
.store(in: &cancellables)

viewModel.output.reportsPublisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { reports in

})
.store(in: &cancellables)

viewModel.output.selectedReportPublisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { selectedReport in

})
.store(in: &cancellables)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Publisher 바인딩 구현이 필요합니다.

모든 publisher의 sink 핸들러가 비어있어 데이터 바인딩이 작동하지 않습니다. PR 설명에 추가 작업이 예정되어 있다고 언급되어 있으므로, 다음 구현들이 필요합니다:

  1. categoryPublisher (line 110): 카테고리 목록 데이터 처리
  2. selectedCategoryPublisher (line 117): 선택된 카테고리로 UI 업데이트
  3. progressPublisher (line 124): 진행 상태 컬렉션뷰에 스냅샷 적용
  4. selectedProgressPublisher (line 131): 선택된 진행 상태 처리
  5. reportsPublisher (line 138): 리포트 목록을 테이블뷰에 스냅샷 적용
  6. selectedReportPublisher (line 145): 선택된 리포트 상세 화면으로 이동

참고로, 유사한 파일인 ReportListHistoryViewController.swift에 일부 구현이 있으니 참고하시기 바랍니다.

추가로, SwiftLint가 플래그한 대로 사용하지 않는 클로저 파라미터들을 _로 대체해주세요 (lines 110, 117, 124, 131, 138, 145).

전체 바인딩 구현을 생성해드릴까요?

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 110-110: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 117-117: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 124-124: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 131-131: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 138-138: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 145-145: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Report/View/ReportHistoryListViewController.swift
around lines 107 to 149, the Combine sink closures are empty and unused closure
parameters should be replaced with `_`; implement each publisher binding as
follows: subscribe to categoryPublisher and update the categories data source
and reload the category UI (use `_` for unused params if any), subscribe to
selectedCategoryPublisher and update selection state in the UI (e.g., highlight
or select the corresponding category cell), subscribe to progressPublisher and
apply a snapshot to the progress collection view data source, subscribe to
selectedProgressPublisher and update selected progress state in the UI,
subscribe to reportsPublisher and apply a snapshot to the table view data source
for reports, and subscribe to selectedReportPublisher and navigate/present the
report detail view for the selected report; ensure all UI updates are performed
on the main thread, store cancellables, and replace unused closure params with
`_` to satisfy SwiftLint.

Comment on lines +248 to +262
private func applyHistorySnapshot(reports: [ReportHistoryItem]) {
let reportsDictionary = Dictionary(grouping: reports) { $0.date ?? "" }
let sortedDates = reportsDictionary.keys.sorted(by: >)

var snapshot = NSDiffableDataSourceSnapshot<String, ReportHistoryItem>()

for dateKey in sortedDates {
snapshot.appendSections([dateKey])
if let items = reportsDictionary[dateKey] {
snapshot.appendItems(items, toSection: dateKey)
}
}

historyDataSource?.apply(snapshot, animatingDifferences: true)
}
Copy link

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

nil/빈 날짜에 대한 헤더 처리 시 크래시 가능성

  • applyHistorySnapshot 에서 Dictionary(grouping: reports) { $0.date ?? "" } 로 그룹핑하고, 키가 빈 문자열("")일 수도 있는 상태로 섹션을 만듭니다.
  • ReportHistoryTableHeaderView.configure(with:)(다른 파일)에서는 dateString.removeLast() 를 호출하는데, 빈 문자열이 들어가면 런타임 크래시가 발생합니다.
  • ReportHistoryItem.date 가 항상 non-nil/non-empty 라는 도메인 보장이 없다면:
    • 날짜가 없는 아이템은 섹션에서 제외하거나,
    • "날짜 미상" 등의 안전한 기본 문자열을 사용하고, header 쪽에서 빈 문자열 처리 가드를 추가하는 것이 좋겠습니다.

예시 수정 방향(컨셉):

let reportsDictionary = Dictionary(grouping: reports.compactMap { $0.date == nil ? nil : $0 }) { $0.date! }

또는 date 가 없으면 별도의 섹션 키(예: "날짜 없음")를 사용하도록 처리.

Also applies to: 278-291


🏁 Script executed:

#!/bin/bash
# Search for ReportHistoryTableHeaderView file and configure method
fd -e swift -x grep -l "ReportHistoryTableHeaderView" {} \;

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Search for removeLast() usage in the codebase, particularly in header view
rg -n "removeLast" --type=swift -B3 -A3

Length of output: 842


🏁 Script executed:

#!/bin/bash
# Search for the configure method that handles date strings
rg -n "configure.*date" --type=swift -i

Length of output: 2366


🏁 Script executed:

#!/bin/bash
# Look at the specific lines mentioned in the review
cat "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift" | head -300 | tail -80

Length of output: 3428


🏁 Script executed:

#!/bin/bash
# Find ReportHistoryTableHeaderView file
fd -e swift "ReportHistoryTableHeaderView"

Length of output: 86


🏁 Script executed:

#!/bin/bash
# Search for where applyHistorySnapshot calls configure or appendSections
rg -n "appendSections\|configure" "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift" -A2 -B2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for ReportHistoryTableHeaderView and check its configure method
fd -e swift -x grep -H "configure.*with" {} \; | grep -i history

Length of output: 987


🏁 Script executed:

#!/bin/bash
# Broader search for ReportHistoryTableHeaderView in the entire project
rg -n "ReportHistoryTableHeaderView" --type=swift

Length of output: 759


🏁 Script executed:

#!/bin/bash
# Check for any file containing "TableHeaderView"
fd -e swift -x grep -l "TableHeaderView" {} \;

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Search for removeLast specifically with context to see if it's guarded
rg -n "removeLast\(\)" --type=swift -B5 -A2

Length of output: 904


🏁 Script executed:

#!/bin/bash
# Look for the header view class definition and configure method
rg -n "class.*ReportHistoryTableHeaderView\|func configure" --type=swift -A10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the actual implementation files in the Report directory
find "Projects/Presentation/Sources/Report" -name "*.swift" -type f | head -20

Length of output: 1529


🏁 Script executed:

#!/bin/bash
# Get list of all Swift files in Report View directory
ls -la "Projects/Presentation/Sources/Report/View/" 2>/dev/null || find "Projects/Presentation/Sources/Report" -type f -name "*.swift" | head -30

Length of output: 768


🏁 Script executed:

#!/bin/bash
# Search for where snapshot sections are created and look for date handling
rg -n "appendSections\|dateKey\|date \?\?" --type=swift -C3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for ReportHistoryItem and its date property definition
rg -n "ReportHistoryItem\|\.date" --type=swift | head -50

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there are any guards related to empty string checks before configure calls
rg -n "isEmpty\|guard.*[\"']{2}" --type=swift | grep -i -E "(history|header|date)" | head -30

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get the entire ReportListHistoryViewController to understand the full context
wc -l "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift"

Length of output: 83


🏁 Script executed:

#!/bin/bash
# Look specifically around line 248-262 and 278-291
sed -n '240,300p' "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift"

Length of output: 2032


🏁 Script executed:

#!/bin/bash
# Look for other places where the section keys (dates) are used
rg -n "snapshot\|section" "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift" -A1 -B1 | head -80

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for where ReportHistoryTableHeaderView is instantiated or configured in the view controller
rg -n "ReportHistoryTableHeaderView\|headerView" "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift" -B2 -A2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for the dequeueReusableHeaderFooterView method which configures header views
rg -n "dequeueReusableHeaderFooterView\|viewForHeaderInSection" "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift" -A10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Final comprehensive check - find all Swift files and search broadly
find "Projects/Presentation/Sources/Report" -name "*.swift" -print0 | xargs -0 grep -l "removeLast" 2>/dev/null || echo "No removeLast found"

Length of output: 86


🏁 Script executed:

#!/bin/bash
# Try a different approach - list all files in the directory
ls -1 "Projects/Presentation/Sources/Report/View/" | grep -i swift || find "Projects/Presentation/Sources" -path "*Report/View*" -name "*.swift"

Length of output: 244


🏁 Script executed:

#!/bin/bash
# Comprehensive search for any usage of removeLast in the entire codebase
find . -name "*.swift" -type f -exec grep -H "removeLast" {} \; 2>/dev/null | head -20

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Look for the specific method that might contain removeLast in any view class
find . -name "*.swift" -type f -exec grep -H "func configure.*with" {} \; 2>/dev/null | head -15

Length of output: 550


🏁 Script executed:

#!/bin/bash
# Search in the entire Projects directory for ReportHistoryTableHeaderView
find "Projects" -name "*.swift" -type f -exec grep -l "ReportHistoryTableHeaderView\|configure.*date.*remove" {} \; 2>/dev/null

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Direct cat of the file to see what we're working with
cat "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift" 2>/dev/null | grep -n "removeLast\|applyHistorySnapshot\|Dictionary\|grouping" | head -30

Length of output: 349


🏁 Script executed:

#!/bin/bash
# Check the entire structure of the Report View directory
find "Projects/Presentation/Sources/Report/View" -type f 2>/dev/null | sort

Length of output: 1296


🏁 Script executed:

#!/bin/bash
# Try accessing the file with different methods to understand project structure
find . -name "ReportListHistoryViewController.swift" -type f 2>/dev/null

Length of output: 81


🏁 Script executed:

#!/bin/bash
# Look for any Swift files with "Header" in the name
find . -name "*Header*.swift" -type f 2>/dev/null | grep -i report

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Search for all Swift files that might contain the header view
find . -name "*.swift" -type f -exec grep -l "dateString.removeLast\|removeLast.*date" {} \; 2>/dev/null

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Get the file structure to understand where things are located
find . -type d -name "Report" 2>/dev/null | head -5

Length of output: 136


🏁 Script executed:

#!/bin/bash
# Look for any removeLast usage with dateString context
grep -r "dateString.*removeLast\|removeLast.*dateString" . --include="*.swift" 2>/dev/null

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Check if ReportHistoryTableHeaderView exists anywhere with configure method
grep -r "class ReportHistoryTableHeaderView\|func configure" . --include="*.swift" 2>/dev/null | head -20

Length of output: 2321


🏁 Script executed:

#!/bin/bash
# Look for the entire codebase structure
ls -la 2>/dev/null | head -20

Length of output: 845


🏁 Script executed:

#!/bin/bash
# Search from root
find / -path "*/Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift" -type f 2>/dev/null || echo "File not found in standard path"

Length of output: 98


🏁 Script executed:

#!/bin/bash
# List current directory to understand the structure
pwd && find . -maxdepth 3 -type d | head -20

Length of output: 352


🏁 Script executed:

#!/bin/bash
# Check if the repo has been cloned properly
ls -la Projects/Presentation/Sources/Report/View/ 2>/dev/null || echo "Directory structure not as expected"

Length of output: 768


🏁 Script executed:

#!/bin/bash
# Find any file with "Report" in name that's a Swift file
find . -name "*Report*" -name "*.swift" -type f 2>/dev/null | head -20

Length of output: 1489


🏁 Script executed:

#!/bin/bash
# Last resort - search entire repo for removeLast and dateString
rg "removeLast|dateString" -t swift 2>/dev/null | head -50

Length of output: 1641


🏁 Script executed:

#!/bin/bash
# Try basic search for any file matching the pattern
find . -type f -name "*.swift" 2>/dev/null | xargs grep -l "applyHistorySnapshot" 2>/dev/null

Length of output: 81


🏁 Script executed:

#!/bin/bash
# Get actual structure
find . -type f -name "*.swift" 2>/dev/null | grep -i "history\|report" | head -30

Length of output: 2238


🏁 Script executed:

#!/bin/bash
# Simple check - find Swift files and search for the method mentioned
find . -type f -name "*.swift" -exec grep -H "applyHistorySnapshot" {} \; 2>/dev/null

Length of output: 294


🏁 Script executed:

#!/bin/bash
# Check cwd and basic structure
echo "=== Current Directory ===" && pwd && echo "=== Files ===" && ls | head -10

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Look for git repository to understand structure
if [ -d ".git" ]; then echo "Git repo found"; git ls-files | grep -i "report.*header\|report.*list.*history" | head -20; else echo "No git repo"; fi

Length of output: 261


🏁 Script executed:

#!/bin/bash
# Simple rg search from root
rg "applyHistorySnapshot" -t swift 2>/dev/null

Length of output: 290


🏁 Script executed:

#!/bin/bash
# Look for all swift files
rg --files -t swift 2>/dev/null | grep -i "history\|report" | head -20

Length of output: 1453


🏁 Script executed:

#!/bin/bash
# Try with ast-grep to find the function
ast-grep --pattern 'func applyHistorySnapshot' 2>/dev/null || echo "ast-grep might not have results"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Direct find all Swift files in the project
rg --files -t swift 2>/dev/null | head -50

Length of output: 3491


🏁 Script executed:

#!/bin/bash
# Search for any Swift file and try to find the class or method
rg "class ReportListHistoryViewController|func applyHistorySnapshot" --type swift 2>/dev/null

Length of output: 323


🏁 Script executed:

#!/bin/bash
# Try to list all files in repo
git ls-files --stage 2>/dev/null | grep swift | head -30 || rg --files | head -50

Length of output: 3179


🏁 Script executed:

#!/bin/bash
# Check if files exist by trying different paths
ls -la "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift" 2>&1

Length of output: 129


🏁 Script executed:

#!/bin/bash
# Better approach - search using simpler commands
find . -type f -name "*.swift" 2>/dev/null | wc -l

Length of output: 3


🏁 Script executed:

#!/bin/bash
# Get first few Swift files found
find . -type f -name "*.swift" 2>/dev/null | head -3

Length of output: 118


🏁 Script executed:

#!/bin/bash
# If we have a file, display it
if [ -f "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift" ]; then 
  echo "File found!"
  sed -n '248,265p' "Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift"
else
  echo "File not found, searching for it..."
  find . -name "ReportListHistoryViewController.swift" 2>/dev/null
fi

Length of output: 686


🏁 Script executed:

#!/bin/bash
# Look for any HeaderView file
find . -type f -name "*HeaderView*" 2>/dev/null | head -10

Length of output: 167


🏁 Script executed:

#!/bin/bash
# Search for configure method in any header-related file
find . -type f -name "*.swift" -exec grep -l "class.*HeaderView.*configure\|func configure.*dateString" {} \; 2>/dev/null

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Final check - what files do we actually have?
git ls-tree -r --name-only HEAD 2>/dev/null | grep -i "\.swift$" | grep -i "report\|history" | head -30

Length of output: 2178


빈 날짜 값이 removeLast() 호출 시 런타임 크래시 유발

applyHistorySnapshot에서 Dictionary(grouping: reports) { $0.date ?? "" }로 그룹핑할 때 빈 문자열이 섹션 키가 되면, viewForHeaderInSection에서 이 빈 문자열을 ReportHistoryTableHeaderView.configure(with:)로 전달합니다. configure 메서드는 dateString.removeLast()를 가드 없이 호출하므로(62-67줄), 빈 문자열이 입력되면 런타임 크래시가 발생합니다.

수정 방향:

  • 빈 문자열/nil 날짜를 필터링하거나, 안전한 기본값("날짜 미상" 등)을 사용
  • 또는 configure 메서드에서 dateString.isEmpty를 체크한 후 removeLast() 호출

예시:

func configure(with dateString: String) {
    guard !dateString.isEmpty else {
        dateLabel.text = "날짜 미상"
        weekLabel.text = ""
        return
    }
    var dateString = dateString
    let weekString = dateString.removeLast()
    dateLabel.text = dateString
    weekLabel.text = String(weekString)
}
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift
around lines 248 to 262: currently grouping uses Dictionary(grouping: reports) {
$0.date ?? "" } which can create an empty-string section key that later causes
ReportHistoryTableHeaderView.configure(with:) to call removeLast() on an empty
string and crash; fix by preventing empty keys or using a safe default and/or by
filtering reports with nil/empty dates before grouping (e.g. group only when
$0.date?.isEmpty == false, and put nil/empty items into a separate "날짜 미상"
section), and also add a guard in ReportHistoryTableHeaderView.configure to
check dateString.isEmpty before calling removeLast() and set a safe label value
when empty.

Comment on lines +34 to +35
private var reports: [ReportHistoryItem] = []

Copy link

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

reports 배열과 fetchReports/fetchReport 연계 누락으로 선택 이벤트 미동작

  • reports 프로퍼티(라인 34)는 fetchReport(index:) 에서만 사용되는데, 어디에서도 값이 채워지지 않아 항상 빈 배열 상태입니다.
  • 현재 fetchReports()reportSubject 에만 ReportHistoryItem.dummyData 를 보내고, reports 배열은 갱신하지 않습니다.
    그 결과:
    • fetchReport(index:)guard index < reports.count 조건이 항상 실패하고,
    • selectedReportSubject 가 절대 emit 되지 않아 상세 화면 진입 흐름이 동작하지 않게 됩니다.

의도한 동작을 위해서는 예를 들어 아래와 같이 수정이 필요합니다:

private func fetchReports() {
    let data = ReportHistoryItem.dummyData
    reports = data           // 원본 캐시 갱신
    reportSubject.send(data) // 리스트 표시
}

또는 실제 API 응답을 받은 위치에서 reportsreportSubject 를 동시에 갱신하도록 맞추는 방식으로 정리해 주세요.

Also applies to: 103-111


🏁 Script executed:

#!/bin/bash
# Find and examine ReportListHistoryViewModel.swift
fd -n "ReportListHistoryViewModel.swift" -x cat {}

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Find and examine ReportListHistoryViewModel.swift
fd "ReportListHistoryViewModel.swift" -x cat {}

Length of output: 3481


fetchReports()에서 reports 배열을 갱신하지 않아 보고서 선택 기능 미동작

검증 결과, 지적된 문제가 확인되었습니다:

  • Line 102-104 fetchReports(): reportSubject에만 더미 데이터를 전송하고, reports 배열은 갱신하지 않음
  • Line 34 reports 배열: 빈 배열로 초기화된 후 어디에서도 값이 할당되지 않음
  • Line 106-109 fetchReport(index:): reports.count가 항상 0이므로 guard index < reports.count 조건이 항상 실패하고, 결과적으로 selectedReportSubject가 절대 emit 되지 않음

이를 수정하려면 fetchReports()에서 다음과 같이 reports 배열도 함께 갱신하세요:

private func fetchReports() {
    let data = ReportHistoryItem.dummyData
    reports = data
    reportSubject.send(data)
}
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift
around lines 34 and 102-109, the reports array is initialized empty and never
populated so fetchReport(index:) always fails; update fetchReports() to assign
the dummy data to the reports property before sending to reportSubject (e.g.,
let data = ReportHistoryItem.dummyData; reports = data;
reportSubject.send(data)) so reports.count reflects the emitted items and
selectedReportSubject can emit correctly.

Comment on lines +67 to +97
private func filterCategory(reportType: ReportType) {
let currentType: ReportType?

if
let previousType = selectedCategorySubject.value,
previousType == reportType
{
currentType = nil
} else {
currentType = reportType
}

selectedCategorySubject.send(currentType)
filterReports()
}

private func filterProgress(progress: ReportProgress) {
var progressItems = progressSubject.value

for i in 0..<progressItems.count {
if progressItems[i].progress == progress {
progressItems[i].isSelected.toggle()
} else {
progressItems[i].isSelected = false
}
}

progressSubject.send(progressItems)

filterReports()
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

❓ Verification inconclusive

필터 로직(카테고리/진행상태)과 filterReports() 구현 보완 필요

  • filterCategoryfilterProgress 모두 마지막에 filterReports() 를 호출하지만, 현재 filterReports() 가 비어 있어 실제 리스트 필터링이 전혀 이뤄지지 않습니다.
  • 이후 구현 시:
    • 원본 reports 배열을 기준으로,
    • selectedCategorySubject.valueprogressSubject.valueisSelected 가 true 인 항목을 조합해서,
    • 필터링된 결과를 reportSubject.send(filtered) 로 내보내는 구조가 자연해 보입니다.
  • 또한 filterProgress 에서 선택된 progress 를 다시 탭하면 isSelected 가 false 로 토글되어 “아무 progress 도 선택되지 않은” 상태가 생길 수 있습니다.
    • “항상 하나는 선택(예: .entire)”이 디자인 요구사항이라면, 동일 progress 탭 시 토글 대신 그대로 유지하는 쪽으로 조건을 조정해야 합니다.

이 부분은 UX/요구사항에 따라 달라질 수 있으니 한 번 논의 후 반영해 주시면 좋겠습니다.

Also applies to: 99-101


I'll verify the concerns raised in this review by examining the actual Swift file.

</shell_scripts_start>


🏁 Script executed:

#!/bin/bash
# Locate and examine the ReportListHistoryViewModel.swift file
find . -name "ReportListHistoryViewModel.swift" -type f

Length of output: 81


🏁 Script executed:

#!/bin/bash
# Display the full content of the ViewModel file to verify the concerns
cat "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift"

Length of output: 3481


🏁 Script executed:

#!/bin/bash
# Search for filterReports implementation and related subjects
rg -n "filterReports|reportSubject|selectedCategorySubject|progressSubject" "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" -A 3 -B 1

Length of output: 1936


🏁 Script executed:

#!/bin/bash
# Check for ReportProgress and ReportType definitions
rg -n "enum ReportProgress|struct ReportProgress|class ReportProgress" --type swift

Length of output: 373


🏁 Script executed:

#!/bin/bash
# Check for ReportType definitions
rg -n "enum ReportType|struct ReportType|class ReportType" --type swift

Length of output: 101


🏁 Script executed:

#!/bin/bash
# Search for any filtering logic or reportSubject assignments
rg -n "reportSubject.send" "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" -B 2 -A 2

Length of output: 117


🏁 Script executed:

#!/bin/bash
# Check line counts around the method definitions to understand context
wc -l "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift"

Length of output: 83


🏁 Script executed:

#!/bin/bash
# Get the context around lines 67-101
sed -n '1,120p' "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift"

Length of output: 3481


🏁 Script executed:

#!/bin/bash
# Search for any CurrentValueSubject or PassthroughSubject declarations
rg -n "CurrentValueSubject|PassthroughSubject" "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" -B 1 -A 1

Length of output: 517


🏁 Script executed:

#!/bin/bash
# Check if there's a reports property
rg -n "var reports|let reports" "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" -B 1 -A 1

Length of output: 351


🏁 Script executed:

#!/bin/bash
# Look for the entire ViewModel class structure
ast-grep --pattern 'class $_ {
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for any computed properties or methods related to filtering
rg -n "private func|public func|var.*:" "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" | head -30

Length of output: 338


🏁 Script executed:

#!/bin/bash
# Get context on the line mentioned "Also applies to: 99-101"
sed -n '95,105p' "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift"

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Look for all subjects and their types in the file
rg -n "@Published|Subject|BehaviorSubject" "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" -A 1

Length of output: 1443


🏁 Script executed:

#!/bin/bash
# Search for init or property initialization
rg -n "init|self\.\w+Subject" "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" -B 1 -A 1

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Get full file for comprehensive analysis
head -200 "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift"

Length of output: 3481


🏁 Script executed:

#!/bin/bash
# Search for any filtering combinations or predicate logic
rg -n "filter|predicate|where" "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" -i

Length of output: 509


🏁 Script executed:

#!/bin/bash
# Look for ReportProgress type and isSelected field
rg -n "isSelected" "Projects/Presentation/Sources/Report/ViewModel/" -r --type swift -B 2 -A 1

Length of output: 1267


🏁 Script executed:

#!/bin/bash
# Find ReportProgress model definition
find . -name "*.swift" -type f | xargs grep -l "struct ReportProgress\|class ReportProgress" 2>/dev/null | head -5

Length of output: 162


🏁 Script executed:

#!/bin/bash
# Search for where progressItems is used
rg -n "progressItems\|progressSubject" "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" -B 1 -A 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for ReportType enum
find . -name "*.swift" -type f | xargs grep -l "enum ReportType" 2>/dev/null | head -5

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Look for any swift files in the Report directory to understand structure
find "Projects/Presentation/Sources/Report" -name "*.swift" -type f | head -20

Length of output: 1529


🏁 Script executed:

#!/bin/bash
# Get comprehensive overview of the ViewModel file structure
cat "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" | head -250

Length of output: 3481


🏁 Script executed:

#!/bin/bash
# Count total lines in the file
wc -l "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift"

Length of output: 83


🏁 Script executed:

#!/bin/bash
# Look at everything after line 90 to understand the complete context
tail -n +90 "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" | head -50

Length of output: 450


🏁 Script executed:

#!/bin/bash
# Final comprehensive dump of entire file
cat "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift"

Length of output: 3481


🏁 Script executed:

#!/bin/bash
# If file exists, check its actual size and content
if [ -f "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift" ]; then
  echo "=== File exists ===" 
  wc -l "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift"
  echo "=== First 300 lines ===" 
  head -300 "Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift"
else
  echo "File not found, listing directory contents:"
  find "Projects" -type f -name "*.swift" 2>/dev/null | grep -i report | head -20
fi

Length of output: 3609


🏁 Script executed:

#!/bin/bash
# Search more broadly in the repository
find . -type f -name "*ViewModel*.swift" | grep -i report

Length of output: 229


🏁 Script executed:

#!/bin/bash
# List all swift files in Projects
find Projects -type f -name "*.swift" 2>/dev/null | head -30

Length of output: 1679


🏁 Script executed:

#!/bin/bash
# Check if Projects directory exists and what's inside
ls -la Projects/Presentation/Sources/Report/ViewModel/ 2>/dev/null || echo "Directory not found"

Length of output: 341


🏁 Script executed:

#!/bin/bash
# Get directory structure
ls -la Projects/Presentation/Sources/Report/ 2>/dev/null || ls -la Projects/ 2>/dev/null || ls -la

Length of output: 278


🏁 Script executed:

#!/bin/bash
# Find Swift files with viewmodel in name
fd -e swift -i viewmodel

Length of output: 1225


🏁 Script executed:

#!/bin/bash
# Look for any Swift files at all
fd -e swift | head -20

Length of output: 1062


🏁 Script executed:

#!/bin/bash
# Basic check
pwd && ls -la

Length of output: 864


🏁 Script executed:

#!/bin/bash
# Check repository structure
find . -maxdepth 3 -type d | head -20

Length of output: 333


🏁 Script executed:

#!/bin/bash
# Final attempt: search all swift files
find . -name "*.swift" 2>/dev/null | grep -i "reportlisthistory" -i

Length of output: 163


🏁 Script executed:

#!/bin/bash
# Grep all swift files for the method names
rg "filterCategory|filterProgress" --type swift -l 2>/dev/null

Length of output: 159


🏁 Script executed:

#!/bin/bash
# Search in all locations for these methods
rg "private func filterCategory" 2>/dev/null

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Check git status to understand repo state
git status 2>/dev/null | head -20 || echo "Not a git repo"

Length of output: 210


🏁 Script executed:

#!/bin/bash
# List what's actually in the current directory
ls -la | head -30

Length of output: 845


🏁 Script executed:

#!/bin/bash
# Check if this is the right branch
git branch 2>/dev/null || echo "Git info not available"

Length of output: 25


🏁 Script executed:

#!/bin/bash
# Find any ReportListHistoryViewModel reference
rg "ReportListHistoryViewModel" 2>/dev/null

Length of output: 753


🏁 Script executed:

#!/bin/bash
# Search for the exact line content from the snippet
rg "filterCategory.*reportType" 2>/dev/null

Length of output: 262


🏁 Script executed:

#!/bin/bash
# Broad search for Swift files
find . -type f -name "*.swift" 2>/dev/null | wc -l

Length of output: 3


🏁 Script executed:

#!/bin/bash
# Show first swift file found
find . -type f -name "*.swift" 2>/dev/null | head -1 | xargs head -50

Length of output: 1163


🏁 Script executed:

#!/bin/bash
# Check if we're in the right location
pwd && git remote -v 2>/dev/null || echo "no git"

Length of output: 155


🏁 Script executed:

#!/bin/bash
# Try absolute path search
find / -name "ReportListHistoryViewModel.swift" 2>/dev/null | head -1

Length of output: 98


🏁 Script executed:

#!/bin/bash
# List all files recursively to understand structure (limited)
find . -type f | head -50

Length of output: 3355


🏁 Script executed:

#!/bin/bash
# Alternative: use locate if available
locate ReportListHistoryViewModel.swift 2>/dev/null || echo "locate not available"

Length of output: 20


filterReports() 메서드 구현 필요 + filterProgress 선택 상태 관리 개선

filterReports() 메서드(99-101줄)가 비어 있어 실제 필터링이 전혀 동작하지 않습니다. 또한 filterProgress() 메서드(86-91줄)에서 선택된 progress를 다시 탭하면 isSelected가 false로 토글되어 "선택된 progress가 없는" 상태가 발생할 수 있습니다. init()에서 .entire 진행상태만 선택된 상태로 초기화되므로, 이러한 동작이 의도된 것인지 명확히 할 필요가 있습니다.

제안:

  • filterReports() 구현: reports 배열을 selectedCategorySubject.valueprogressSubject.value(isSelected가 true인 항목)를 조합하여 필터링한 후 reportSubject.send(filtered) 호출
  • filterProgress() 동작 조정: 항상 최소 하나의 progress가 선택된 상태를 유지해야 한다면, 동일 항목 탭 시 토글 대신 그대로 유지하는 조건 추가

Also applies to: 99-101

Copy link
Contributor

@choijungp choijungp 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 13 to 14
let thumbnailUrl: String?
let date: String?
Copy link
Contributor

Choose a reason for hiding this comment

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

요고 2개가 옵셔널인 이유가 무엇인가용 ??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  1. reportEntity에서 ReportHistoryItem 과정에서 썸네일 이미지를 뽑을때, 배열.first 와 같은 형식으로 url을 뽑을 걸 예상했습니다. 이때 String 옵셔널 타입으로 가져올 테니까 Item의 thumbnailUrl도 String? 으로 정의했습니다. 또한 이런 경우는 없을테지만, 혹시라도 저장 시에 이미지 url없이 올라가거나, url없이 응답을 내려준 경우도 있을 수 있지 않을까 하여 옵셔널로 정의했는데요, 조금 과한 부분도 있는것 같긴합니다! 조이의 의견은 어떠신지~ 빼는게 나을까요?

  2. 아마 dateformatter로 제보의 날짜, 요일 형식의 문자열을 생성하지 않을까 했는데요, 결과값이 마찬가지로 String 옵셔널로 예상되어 이렇게 정의했습니다. 하지만 아예 옵셔널로 가지고 있지 않고, VM에서 변환 실패 시에 적절한 처리를 하는게 좋을 것 같기도 합니다! 마찬가지로 의견이 궁금합니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

당장에 필요 없다면 옵셔널을 빼도 괜찮을 것 같지만 ~~ 일단 급하지 않은 것이니 서버 연결하면서 수정해도 괜찮을 것 같아요 !!

Comment on lines 65 to 67
} else {
countText = ""
}
Copy link
Contributor

Choose a reason for hiding this comment

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

만약 개수가 없다면
제보 완료 이런식으로 표기되는건가용 ?? 제보 완료 0 이 아닌 ?!??!?!

Copy link
Contributor

Choose a reason for hiding this comment

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

앗 ?? historyCategory는 무엇일까요 ??? Progress랑 헷갈렷어유 !!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  1. 네 맞아요 요구사항이 그렇게 되는 것으로 확인했는데 다시 확인해보겠습니다!

  2. 조이 말씀대로, progress가 맞아요,, 중간에 네이밍 수정을 했었는데 수정하면서 저도 헷갈린거 같습니다 수정하겠습니다!

Comment on lines 185 to 187
progressLabel.textColor = item.progress.titleColor
progressLabel.text = item.progress.description

Copy link
Contributor

Choose a reason for hiding this comment

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

진행상황 폰트가 어색해보이는거눈 요기 font 적용이 안되가주구 그른거 아닐까요 ??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

맞는거 같아요!! 이따 확인해보겠습니다

매의 눈이시군요,,!!!

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

🧹 Nitpick comments (4)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift (2)

80-88: Cell의 서브뷰를 contentView에 추가하는 것을 권장합니다.

현재 addSubview(containerView)로 self에 직접 추가하고 있습니다. UITableViewCell에서는 contentView에 서브뷰를 추가하는 것이 표준 패턴입니다. ReportHistoryTableHeaderView에서 이미 contentView 패턴을 사용하고 있으므로 일관성을 위해 여기서도 동일하게 적용하는 것이 좋습니다.

다음과 같이 수정할 수 있습니다:

 private func configureLayout() {
-    addSubview(containerView)
+    contentView.addSubview(containerView)
     containerView.addSubview(progressView)

185-188: progressLabel 폰트 설정 위치 개선 제안

현재 configure(with:) 메서드 내에서 폰트를 설정하고 있는데, 이는 셀이 재사용될 때마다 동일한 폰트를 반복 설정하게 됩니다. 폰트는 변하지 않는 속성이므로 configureAttribute() 메서드로 이동하는 것이 더 적절합니다.

다음과 같이 수정할 수 있습니다:

 private func configureAttribute() {
     // ... existing code ...
     
     dotView.backgroundColor = BitnagilColor.gray90
     dotView.layer.cornerRadius = Layout.dotViewSize / 2
     dotView.layer.masksToBounds = true
+    
+    progressLabel.font = BitnagilFont.init(style: .caption1, weight: .semiBold).font
 }

그리고 configure(with:) 메서드에서는 폰트 설정을 제거:

 func configure(with item: ReportHistoryItem) {
     progressView.backgroundColor = item.progress.backgroundColor
     
     progressLabel.textColor = item.progress.titleColor
     progressLabel.text = item.progress.description
-    progressLabel.font = BitnagilFont.init(style: .caption1, weight: .semiBold).font
Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift (2)

34-34: reports 배열과 reportSubject 동기화 필요

fetchReports() 메서드가 reportSubject에만 더미 데이터를 전송하고 있어, reports 배열은 항상 비어있는 상태입니다. 이로 인해 fetchReport(index:) 메서드의 guard index < reports.count 조건이 항상 실패하여 상세 화면 진입이 불가능합니다.

PR 설명에서 내일 추가 작업 예정이라고 하셨는데, 해당 작업 시 아래와 같이 동기화를 고려해주세요.

 private func fetchReports() {
+    let data = ReportHistoryItem.dummyData
+    reports = data
-    reportSubject.send(ReportHistoryItem.dummyData)
+    reportSubject.send(data)
 }

Also applies to: 103-111


83-101: filterReports() 구현 및 filterProgress 선택 상태 로직 검토 필요

두 가지 사항을 검토해주세요:

  1. filterReports() 구현: 현재 빈 메서드로 실제 필터링이 동작하지 않습니다. 구현 시 reports 배열을 selectedCategorySubject.valueprogressSubject.value(isSelected가 true인 항목)를 기준으로 필터링하여 reportSubject로 전송하는 로직이 필요합니다.

  2. filterProgress 선택 상태: 현재 선택된 progress를 다시 탭하면 isSelected가 false로 토글되어 "아무것도 선택되지 않은" 상태가 될 수 있습니다. init()에서 .entire만 선택된 상태로 초기화되므로, 항상 최소 하나의 progress가 선택되어야 하는 UX라면 동일 항목 재선택 시 토글하지 않도록 조건을 조정하는 것을 고려해주세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63e7c59 and c33676c.

📒 Files selected for processing (6)
  • Projects/Domain/Sources/Entity/Enum/ReportProgress.swift (1 hunks)
  • Projects/Domain/Sources/Entity/ReportEntity.swift (1 hunks)
  • Projects/Presentation/Sources/Report/Model/ReportHistoryItem.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift (1 hunks)
  • Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift (1 hunks)
  • Projects/Presentation/Sources/Report/ViewModel/ReportListHistoryViewModel.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Projects/Presentation/Sources/Report/Model/ReportHistoryItem.swift
🧰 Additional context used
🧠 Learnings (3)
📚 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/ReportHistoryTableViewCell.swift
  • Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.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/ReportHistoryTableHeaderView.swift
📚 Learning: 2025-07-16T10:57:16.559Z
Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.

Applied to files:

  • Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift
🧬 Code graph analysis (1)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift (2)
Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift (2)
  • configureAttribute (53-78)
  • configure (182-201)
Projects/Presentation/Sources/Report/View/ReportListHistoryViewController.swift (1)
  • configureAttribute (55-69)
⏰ 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 (4)
Projects/Domain/Sources/Entity/Enum/ReportProgress.swift (1)

8-26: 이전 리뷰 피드백이 반영되어 코드가 깔끔합니다!

enum 설계가 명확하고, 모든 케이스의 description 문자열이 일관된 스타일(공백 포함)로 통일되어 있습니다. 이전에 지적된 "처리완료" → "처리 완료" 수정도 잘 반영되었습니다.

Projects/Domain/Sources/Entity/ReportEntity.swift (1)

8-38: 이전 리뷰 피드백이 모두 반영되었습니다!

모든 프로퍼티가 public let으로 선언되고 명시적인 public init이 추가되어 다른 모듈(Presentation, DataSource 등)에서 ReportEntity를 완전하게 사용할 수 있게 되었습니다. LocationEntity, EmotionEntity 등 다른 도메인 엔티티들과 일관된 패턴을 따르고 있어 좋습니다.

Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift (1)

41-72: 이전 리뷰 피드백이 잘 반영되었습니다!

  1. 서브뷰를 contentView에 추가하는 UIKit 표준 패턴을 따르고 있습니다.
  2. configure(with:) 메서드에서 guard let weekCharacter = dateString.last로 안전하게 문자열을 처리하여 빈 문자열에 대한 크래시를 방지하고 있습니다.

구현이 방어적이고 명확합니다!

Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableViewCell.swift (1)

75-77: 이전 리뷰 피드백이 반영되었습니다!

dotView.layer.cornerRadiusLayout.dotViewSize / 2를 사용하도록 수정되어 초기화 시점의 bounds 문제가 해결되었습니다.

@taipaise taipaise merged commit 090a6d4 into develop Nov 20, 2025
2 checks passed
@taipaise taipaise deleted the feat/report branch November 20, 2025 12:15
@taipaise taipaise changed the title Feat/report [Feat-T3-201] 제보 히스토리 화면 구현 (1차) Nov 20, 2025
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.

3 participants