Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Projects/DataSource/Sources/DTO/ReportDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,20 @@ struct ReportDTO: Decodable {
address: reportLocation),
photoUrls: reportImageUrls ?? [])
}

func toReportEntity(date: String) throws -> ReportEntity {
guard let reportId else { throw NetworkError.decodingError }
return ReportEntity(
id: reportId,
title: reportTitle,
date: date,
type: ReportType(rawValue: reportCategory) ?? .transportation,
progress: ReportProgress(rawValue: reportStatus) ?? .received,
content: reportContent,
location: LocationEntity(
longitude: longitude,
latitude: latitude,
address: reportLocation),
photoUrls: reportImageUrls ?? [])
}
}
10 changes: 10 additions & 0 deletions Projects/DataSource/Sources/DTO/ReportDictonaryDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// ReportDictonaryDTO.swift
// DataSource
//
// Created by 최정인 on 11/21/25.
//

struct ReportDictonaryDTO: Decodable {
let reportInfos: [String: [ReportDTO]]
}
Comment on lines +1 to +10
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

파일명과 타입명의 오타를 수정하세요.

파일명 ReportDictonaryDTO.swift와 구조체명 ReportDictonaryDTO에 오타가 있습니다. "Dictonary"는 "Dictionary"로 수정되어야 합니다.

다음과 같이 수정하세요:

  1. 파일명을 ReportDictionaryDTO.swift로 변경
  2. 구조체명을 ReportDictionaryDTO로 변경
  3. 이 타입을 사용하는 모든 곳에서 이름 업데이트 필요 (예: ReportRepository.swift Line 19, ReportEndpoint.swift)
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/DTO/ReportDictonaryDTO.swift lines 1–10 the file
and struct name have a typo ("Dictonary" → "Dictionary"); rename the file to
ReportDictionaryDTO.swift and update the struct declaration to struct
ReportDictionaryDTO: Decodable { let reportInfos: [String: [ReportDTO]] }, then
search and replace all usages/imports across the codebase (e.g.,
ReportRepository.swift at around line 19, ReportEndpoint.swift and any tests or
references) to the new type name to ensure compilation.

9 changes: 6 additions & 3 deletions Projects/DataSource/Sources/Endpoint/ReportEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,30 @@
//

enum ReportEndpoint {
case fetchReports
case fetchReportDetail(reportId: Int)
}

extension ReportEndpoint: Endpoint {
var baseURL: String {
switch self {
case .fetchReportDetail:
case .fetchReports, .fetchReportDetail:
return AppProperties.baseURL + "/api/v2/reports"
}
}

var path: String {
switch self {
case .fetchReports:
return baseURL
case .fetchReportDetail(let reportId):
"\(baseURL)/\(reportId)"
return "\(baseURL)/\(reportId)"
}
}

var method: HTTPMethod {
switch self {
case .fetchReportDetail:
case .fetchReports, .fetchReportDetail:
.get
}
}
Expand Down
18 changes: 17 additions & 1 deletion Projects/DataSource/Sources/Repository/ReportRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,25 @@ final class ReportRepository: ReportRepositoryProtocol {

}

func fetchReports() async throws -> [ReportEntity] {
let endpoint = ReportEndpoint.fetchReports
guard let response = try await networkService.request(endpoint: endpoint, type: ReportDictonaryDTO.self)
else { return [] }

var reportEntities: [ReportEntity] = []
for (date, reports) in response.reportInfos {
let reportHistories = reports.compactMap({ try? $0.toReportEntity(date: date) })
reportEntities += reportHistories
}

return reportEntities
}

func fetchReportDetail(reportId: Int) async throws -> ReportEntity? {
let endpoint = ReportEndpoint.fetchReportDetail(reportId: reportId)
guard let response = try await networkService.request(endpoint: endpoint, type: ReportDTO.self) else { return nil }
guard let response = try await networkService.request(endpoint: endpoint, type: ReportDTO.self)
else { return nil }

return try response.toReportEntity()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
public protocol ReportRepositoryProtocol {
func report(reportEntity: ReportEntity) async

/// 제보 목록을 조회합니다.
/// - Returns: 조회된 제보 목록
func fetchReports() async throws -> [ReportEntity]

/// 제보 상세 기록을 조회합니다.
/// - Parameter reportId: 조회할 제보의 ID
/// - Returns: 조회된 제보
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,11 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol {
return ReportRegistrationViewModel(reportUseCase: reportUseCase)
}

DIContainer.shared
.register(type: ReportHistoryViewModel.self) { container in
return ReportHistoryViewModel()
DIContainer.shared.register(type: ReportHistoryViewModel.self) { container in
guard let reportRepository = container.resolve(type: ReportRepositoryProtocol.self)
else { fatalError("reportRepository 의존성이 등록되지 않았습니다.") }

return ReportHistoryViewModel(reportRepository: reportRepository)
}

DIContainer.shared.register(type: ReportDetailViewModel.self) { container in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Combine
import Domain
import Shared
import SnapKit
import UIKit

Expand Down Expand Up @@ -262,7 +263,6 @@ final class ReportHistoryViewController: BaseViewController<ReportHistoryViewMod
var snapshot = NSDiffableDataSourceSnapshot<ProgressSection, ReportProgressItem>()
snapshot.appendSections([.main])
snapshot.appendItems(items, toSection: .main)
print(items)
progressDataSource?.apply(snapshot, animatingDifferences: true)
}

Expand Down Expand Up @@ -316,6 +316,28 @@ extension ReportHistoryViewController: UITableViewDelegate {

return header
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let snapshot = historyDataSource?.snapshot()
else { return }

let sectionID = snapshot.sectionIdentifiers[indexPath.section]
let items = snapshot.itemIdentifiers(inSection: sectionID)

guard indexPath.row < items.count
else { return }

let item = items[indexPath.row]

guard let reportDetailViewModel = DIContainer.shared.resolve(type: ReportDetailViewModel.self)
else { fatalError("reportDetailViewModel 의존성이 등록되지 않았습니다.") }

let reportDetailViewController = ReportDetailViewController(viewModel: reportDetailViewModel, reportId: item.id)
reportDetailViewController.hidesBottomBarWhenPushed = true

self.navigationController?.pushViewController(reportDetailViewController, animated: true)
tableView.deselectRow(at: indexPath, animated: true)
}
}

extension ReportHistoryViewController: ReportCategoryTableViewControllerDelegate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import Foundation
final class ReportHistoryViewModel: ViewModel {
enum Input {
case fetchReports
case fetchReport(index: Int)
case filterCategory(type: ReportType)
case filterProgress(progress: ReportProgress)
}

struct Output {
let progressPublisher: AnyPublisher<[ReportProgressItem], Never>
let selectedProgressPublisher: AnyPublisher<ReportProgress?, Never>
let categoryPublisher: AnyPublisher<[ReportType], Never>
let selectedCategoryPublisher: AnyPublisher<ReportType?, Never>
let reportsPublisher: AnyPublisher<[ReportHistoryItem], Never>
Expand All @@ -27,14 +27,17 @@ final class ReportHistoryViewModel: ViewModel {

private(set) var output: Output
private let progressSubject = CurrentValueSubject<[ReportProgressItem], Never>([])
private let selectedProgressSubject = CurrentValueSubject<ReportProgress?, Never>(nil)
private let categorySubject = CurrentValueSubject<[ReportType], Never>([])
private let selectedCategorySubject = CurrentValueSubject<ReportType?, Never>(nil)
private let reportSubject = CurrentValueSubject<[ReportHistoryItem], Never>([])
private let selectedReportSubject = PassthroughSubject<Int?, Never>()
private(set) var selectedReportCategory: ReportType?
private var reports: [ReportHistoryItem] = []
private let reportRepository: ReportRepositoryProtocol

init() {
init(reportRepository: ReportRepositoryProtocol) {
self.reportRepository = reportRepository
progressSubject
.send(
ReportProgress.allCases.map { ReportProgressItem(
Expand All @@ -46,6 +49,7 @@ final class ReportHistoryViewModel: ViewModel {

output = Output(
progressPublisher: progressSubject.eraseToAnyPublisher(),
selectedProgressPublisher: selectedProgressSubject.eraseToAnyPublisher(),
categoryPublisher: categorySubject.eraseToAnyPublisher(),
selectedCategoryPublisher: selectedCategorySubject.eraseToAnyPublisher(),
reportsPublisher: reportSubject.eraseToAnyPublisher(),
Expand All @@ -56,8 +60,6 @@ final class ReportHistoryViewModel: ViewModel {
switch input {
case .fetchReports:
fetchReports()
case .fetchReport(let index):
fetchReport(index: index)
case .filterCategory(let type):
filterCategory(reportType: type)
case .filterProgress(let progress):
Expand Down Expand Up @@ -99,16 +101,42 @@ final class ReportHistoryViewModel: ViewModel {
}

private func filterReports() {
let selectedProgress = selectedProgressSubject.value
let selectedCategory = selectedCategorySubject.value

var filteredReports = reports
if let selectedProgress {
filteredReports = filteredReports.filter({ $0.progress == selectedProgress })
}
if let selectedCategory {
filteredReports = filteredReports.filter({ $0.type == selectedCategory })
}
reportSubject.send(filteredReports)
}
Comment on lines 103 to 115
Copy link

@coderabbitai coderabbitai bot Nov 21, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

필터링 로직의 잠재적 문제를 확인하세요.

현재 구현에서 selectedProgressSubject.value를 사용하고 있지만, filterProgress 메서드에서 이 값이 업데이트되지 않아 진행 상태 필터링이 의도대로 작동하지 않을 수 있습니다.

다음 스크립트로 selectedProgressSubject가 설정되는 위치를 확인하세요:


🏁 Script executed:

#!/bin/bash
# selectedProgressSubject.send 호출을 찾아 올바르게 업데이트되는지 확인
rg -n "selectedProgressSubject\.send" --type swift

Length of output: 0


추가 검증이 필요합니다. selectedProgressSubject의 전체 사용 패턴을 확인하겠습니다.


🏁 Script executed:

#!/bin/bash
# selectedProgressSubject의 모든 사용 위치 확인
rg -n "selectedProgressSubject" --type swift -B2 -A2

Length of output: 2071


🏁 Script executed:

#!/bin/bash
# filterProgress 메서드 존재 여부 확인
rg -n "filterProgress" --type swift -B2 -A2

Length of output: 2222


🏁 Script executed:

#!/bin/bash
# ReportHistoryViewModel.swift 파일의 전체 내용 확인
find . -name "ReportHistoryViewModel.swift" -type f | head -1 | xargs cat

Length of output: 5081


filterProgress 메서드에서 selectedProgressSubject 업데이트 누락

filterProgress 메서드(87-99줄)에서 선택된 진행 상태를 업데이트하지 않습니다. filterCategory 메서드(84줄)는 selectedCategorySubject.send(currentType)을 호출하여 선택된 카테고리를 업데이트하지만, filterProgress는 이에 해당하는 selectedProgressSubject.send() 호출이 없습니다.

결과적으로 filterReports(104줄)에서 selectedProgressSubject.value를 읽어도 항상 nil이므로 진행 상태 필터링이 작동하지 않습니다.

수정: filterProgress 메서드 98줄 후에 선택된 진행 상태를 업데이트하는 로직을 추가하세요.

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Report/ViewModel/ReportHistoryViewModel.swift
around lines 103–115, filterReports reads selectedProgressSubject.value but
filterProgress (lines ~87–99) never updates that subject; add a call to
selectedProgressSubject.send(currentProgress) (or the actual progress variable
used in filterProgress) immediately after the currentProgress is determined
(after line 98) so the selectedProgressSubject reflects the chosen progress and
filterReports can filter correctly.

Copy link
Collaborator

Choose a reason for hiding this comment

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

확인했고 오늘 작업에서 구현하겠습니다

Copy link

Choose a reason for hiding this comment

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

@taipaise 확인해 주셔서 감사합니다! 오늘 작업에서 selectedProgressSubject.send() 호출을 추가하시면 진행 상태 필터링이 정상적으로 작동할 것입니다. 구현하시면서 궁금하신 점이 있으시면 언제든지 말씀해 주세요. 😊


private func fetchReports() {
//reportSubject.send(ReportHistoryItem.dummyData)
}

private func fetchReport(index: Int) {
guard index < reports.count else { return }

selectedReportSubject.send(reports[index].id)
Task {
do {
let reportEntities = try await reportRepository.fetchReports()

var reportHistoryItems: [ReportHistoryItem] = []
for reportEntity in reportEntities {
let reportHistoryItem = ReportHistoryItem(
id: reportEntity.id,
title: reportEntity.title,
thumbnailUrl: reportEntity.photoUrls.first ?? "",
date: reportEntity.date ?? "",
type: reportEntity.type,
progress: reportEntity.progress,
location: reportEntity.location.address ?? "")
reportHistoryItems.append(reportHistoryItem)
}

reports = reportHistoryItems
reportSubject.send(reportHistoryItems)
} catch {
// TODO: 에러 처리
}
}
}
}