From d8d72c6d00e69715a6d8cc19c4f2e3d298833d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=A5=E1=86=BC=E1=84=8B?= =?UTF-8?q?=E1=85=B5=E1=86=AB?= Date: Fri, 21 Nov 2025 18:13:42 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20=EC=A0=9C=EB=B3=B4=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EC=84=B1=20=EC=9A=94=EC=86=8C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#T3-203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataSource/Sources/DTO/ReportDTO.swift | 16 ++++++++++++++++ .../Sources/DTO/ReportDictonaryDTO.swift | 10 ++++++++++ .../Sources/Endpoint/ReportEndpoint.swift | 9 ++++++--- .../Sources/Repository/ReportRepository.swift | 18 +++++++++++++++++- .../Repository/ReportRepositoryProtocol.swift | 4 ++++ 5 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 Projects/DataSource/Sources/DTO/ReportDictonaryDTO.swift diff --git a/Projects/DataSource/Sources/DTO/ReportDTO.swift b/Projects/DataSource/Sources/DTO/ReportDTO.swift index 99c3287..882ccbd 100644 --- a/Projects/DataSource/Sources/DTO/ReportDTO.swift +++ b/Projects/DataSource/Sources/DTO/ReportDTO.swift @@ -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 ?? []) + } } diff --git a/Projects/DataSource/Sources/DTO/ReportDictonaryDTO.swift b/Projects/DataSource/Sources/DTO/ReportDictonaryDTO.swift new file mode 100644 index 0000000..7828cdb --- /dev/null +++ b/Projects/DataSource/Sources/DTO/ReportDictonaryDTO.swift @@ -0,0 +1,10 @@ +// +// ReportDictonaryDTO.swift +// DataSource +// +// Created by 최정인 on 11/21/25. +// + +struct ReportDictonaryDTO: Decodable { + let reportInfos: [String: [ReportDTO]] +} diff --git a/Projects/DataSource/Sources/Endpoint/ReportEndpoint.swift b/Projects/DataSource/Sources/Endpoint/ReportEndpoint.swift index 489339d..f6d451f 100644 --- a/Projects/DataSource/Sources/Endpoint/ReportEndpoint.swift +++ b/Projects/DataSource/Sources/Endpoint/ReportEndpoint.swift @@ -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 } } diff --git a/Projects/DataSource/Sources/Repository/ReportRepository.swift b/Projects/DataSource/Sources/Repository/ReportRepository.swift index c8a57f8..010b217 100644 --- a/Projects/DataSource/Sources/Repository/ReportRepository.swift +++ b/Projects/DataSource/Sources/Repository/ReportRepository.swift @@ -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() } } diff --git a/Projects/Domain/Sources/Protocol/Repository/ReportRepositoryProtocol.swift b/Projects/Domain/Sources/Protocol/Repository/ReportRepositoryProtocol.swift index 9d0789b..9e5d06c 100644 --- a/Projects/Domain/Sources/Protocol/Repository/ReportRepositoryProtocol.swift +++ b/Projects/Domain/Sources/Protocol/Repository/ReportRepositoryProtocol.swift @@ -8,6 +8,10 @@ public protocol ReportRepositoryProtocol { func report(reportEntity: ReportEntity) async + /// 제보 목록을 조회합니다. + /// - Returns: 조회된 제보 목록 + func fetchReports() async throws -> [ReportEntity] + /// 제보 상세 기록을 조회합니다. /// - Parameter reportId: 조회할 제보의 ID /// - Returns: 조회된 제보 From de301cd6d98434719bbdf31e67ef8e7c2eecad90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8E=E1=85=AC=E1=84=8C=E1=85=A5=E1=86=BC=E1=84=8B?= =?UTF-8?q?=E1=85=B5=E1=86=AB?= Date: Fri, 21 Nov 2025 18:29:39 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Feat:=20=EC=A0=9C=EB=B3=B4=20=ED=9E=88?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EB=A6=AC=20Domain=20-=20Presentation=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20(#T3-203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PresentationDependencyAssembler.swift | 8 +-- .../View/ReportHistoryViewController.swift | 24 ++++++++- .../ViewModel/ReportHistoryViewModel.swift | 50 +++++++++++++++---- 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift b/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift index a7f33f6..fcd20a2 100644 --- a/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift +++ b/Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift @@ -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 diff --git a/Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swift b/Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swift index dd23463..25eb2f7 100644 --- a/Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swift +++ b/Projects/Presentation/Sources/Report/View/ReportHistoryViewController.swift @@ -7,6 +7,7 @@ import Combine import Domain +import Shared import SnapKit import UIKit @@ -262,7 +263,6 @@ final class ReportHistoryViewController: BaseViewController() snapshot.appendSections([.main]) snapshot.appendItems(items, toSection: .main) - print(items) progressDataSource?.apply(snapshot, animatingDifferences: true) } @@ -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 { diff --git a/Projects/Presentation/Sources/Report/ViewModel/ReportHistoryViewModel.swift b/Projects/Presentation/Sources/Report/ViewModel/ReportHistoryViewModel.swift index 6a5fdd4..6769571 100644 --- a/Projects/Presentation/Sources/Report/ViewModel/ReportHistoryViewModel.swift +++ b/Projects/Presentation/Sources/Report/ViewModel/ReportHistoryViewModel.swift @@ -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 let categoryPublisher: AnyPublisher<[ReportType], Never> let selectedCategoryPublisher: AnyPublisher let reportsPublisher: AnyPublisher<[ReportHistoryItem], Never> @@ -27,14 +27,17 @@ final class ReportHistoryViewModel: ViewModel { private(set) var output: Output private let progressSubject = CurrentValueSubject<[ReportProgressItem], Never>([]) + private let selectedProgressSubject = CurrentValueSubject(nil) private let categorySubject = CurrentValueSubject<[ReportType], Never>([]) private let selectedCategorySubject = CurrentValueSubject(nil) private let reportSubject = CurrentValueSubject<[ReportHistoryItem], Never>([]) private let selectedReportSubject = PassthroughSubject() 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( @@ -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(), @@ -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): @@ -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) } 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: 에러 처리 + } + } } }