Skip to content

SPM-175 주문관리 구현 / 주문 로직 구현#15

Merged
Sangyoon98 merged 4 commits intodevfrom
SPM-175
Oct 22, 2025
Merged

SPM-175 주문관리 구현 / 주문 로직 구현#15
Sangyoon98 merged 4 commits intodevfrom
SPM-175

Conversation

@Sangyoon98
Copy link
Copy Markdown
Member

@Sangyoon98 Sangyoon98 commented Oct 21, 2025

📝 Summary

  • 주문 관리 화면 구현
  • 주문 관리 상세 화면 구현
  • 장바구니 주문 로직 구현
  • 장바구니 주문 완료 화면 구현
  • 주문 취소 구현
  • 주문 입고 처리 구현

🙏 Question & PR point

📬 Reference

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 주문 관리: 주문 목록, 상세 조회, 생성, 수령, 취소 기능 추가
    • 주문 처리 흐름: 장바구니에서 주문 생성 및 주문 결과 하단 시트 제공
    • UI 컴포넌트: 상태 배지(StatusChip), 주문 상세/목록 화면, 주문 제목·날짜 포매터, 문자열 리소스 추가
    • 네비게이션: 주문/부품 탭별 독립 네비게이션 경로 구현
  • 버그 수정 / 개선

    • 장바구니 주문 처리 상태 및 오류 표시 흐름 개선
  • 스타일

    • 상태용 색상 자산(대기/완료/취소) 및 버튼 색상 조정

Copy link
Copy Markdown

@yangjiseonn yangjiseonn left a comment

Choose a reason for hiding this comment

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

확인했습니다!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 21, 2025

Walkthrough

주문(Order) 도메인(모델·DTO·API·레포·유스케이스·UI)과 관련된 파일들을 신규 추가·연결하고, ContentView의 네비게이션 상태를 Orders/Parts로 분리하며 Cart 관련 뷰/뷰모델에 주문 생성 흐름을 통합했습니다. 색상 자산·유틸·문구 리소스와 일부 UI 컴포넌트도 추가/수정되었습니다.

Changes

동료집단 / 파일(들) 변경 요약
앱 진입 · 네비게이션 · DI
SampoomManagement/App/ContentView.swift, SampoomManagement/Core/DI/AppDependencies.swift
ContentView의 navigationPath를 ordersNavigationPathpartsNavigationPath로 분리하고 Orders/Parts 탭을 각각 NavigationStack에 바인딩; AppDependencies에 OrderAPI/Repository/UseCase들을 추가하고 Order 관련 ViewModel 팩토리 메서드 추가, CartListViewModel에 createOrderUseCase 주입
주문 — 도메인 모델
.../Features/Order/Domain/Models/Order.swift, OrderList.swift, OrderStatus.swift
Order, OrderCategory, OrderGroup, OrderPart, OrderList 모델 및 OrderStatus 열거형 추가
주문 — 데이터 / 네트워크
.../Features/Order/Data/Remote/DTO/OrderDto.swift, .../API/OrderAPI.swift, .../Repository/OrderRepositoryImpl.swift, .../Mappers/OrderMappers .swift
DTO(OrderDto 등) 추가; OrderAPI 네트워크 래퍼(5개 엔드포인트) 구현; OrderRepositoryImpl 구현; DTO→도메인 매핑 확장 메서드 추가
주문 — UI (리스트/상세/뷰모델/상태/이벤트)
.../Features/Order/UI/OrderListView.swift, OrderListViewModel.swift, OrderListUiState.swift, OrderListUiEvent.swift, OrderDetailView.swift, OrderDetailViewModel.swift, OrderDetailUiState.swift, OrderDetailUiEvent.swift, OrderDetailContent.swift, OrderResultBottomSheet.swift
OrderList/OrderDetail 관련 뷰, 뷰모델, 상태, 이벤트 및 상세 콘텐츠·결과 바텀시트 구현; 상세에서 취소·수령 흐름 처리
Cart (UI/로직 연동)
.../Features/Cart/UI/CartListView.swift, CartListViewModel.swift, CartListUiState.swift, CartListUiEvent.swift
CartListView 리팩토링(모듈화, MainNavigationContent), 주문 생성(createOrderUseCase) 실제 처리 로직 추가, UI 상태(isProcessing/processError/processedOrder) 및 dismissOrderResult 이벤트 추가
UI 컴포넌트 · 유틸 · 리소스
SampoomManagement/Core/UI/Components/StatusChip.swift, Core/Utilities/OrderFormatter.swift, Core/Utilities/DateFormatterUtil.swift, Core/Resources/StringResources.swift
상태 배지 컴포넌트(StatusChip) 추가, 주문 제목 빌드 유틸(OrderFormatter), 날짜 포맷 유틸(DateFormatterUtil), StringResources.Order 문자열 추가
자산(색상)
Resources/Assets.xcassets/FailRed.colorset/..., SuccessGreen.colorset/..., WaitYellow.colorset/...
FailRed/SuccessGreen/WaitYellow 색상 자산 추가
사소한 변경(저자·스타일·주석)
여러 Features/Cart/... 파일들(헤더 저자 변경), Features/Part/UI/PartDetailBottomSheetView.swift, Core/Network/NetworkManager.swift
Cart 파일 헤더 저자명 변경(메타데이터); Part 버튼 색상 변경(blue → accent); NetworkManager 내 주석 라인 제거

Sequence Diagram(s)

sequenceDiagram
    %% 색상: new/changed 흐름 강조(연두/파랑) - 접근성 고려, 색상 단순 사용
    participant User
    participant ContentView
    participant OrderListView
    participant OrderListViewModel
    participant GetOrderUseCase
    participant OrderRepository
    participant OrderAPI
    participant Network

    User->>ContentView: 주문 탭 선택
    ContentView->>OrderListView: show (ordersNavigationPath)
    OrderListView->>OrderListViewModel: onEvent(.loadOrderList)
    OrderListViewModel->>GetOrderUseCase: execute()
    GetOrderUseCase->>OrderRepository: getOrderList()
    OrderRepository->>OrderAPI: getOrderList()
    OrderAPI->>Network: GET /agency/1/orders
    Network-->>OrderAPI: [OrderDto]
    OrderAPI-->>OrderRepository: [OrderDto]
    OrderRepository->>OrderRepository: DTO → Model (toModel)
    OrderRepository-->>GetOrderUseCase: OrderList
    GetOrderUseCase-->>OrderListViewModel: OrderList
    OrderListViewModel-->>OrderListView: uiState 업데이트 및 렌더링
Loading

(상세 화면에서 취소·수령 흐름은 동일한 패턴으로 OrderDetailViewModel → 해당 UseCase → Repository → API → Network 호출이 발생합니다.)

Estimated code review effort

🎯 3 (중간) | ⏱️ ~40분

근거: 주문 기능 전체 계층을 신규로 추가하고 DI·네비게이션·Cart 연동까지 포함된 중간 규모 변경. 파일 수는 많지만 패턴은 비교적 일관적이며, UI와 비동기 로직 검토가 필요함.

Possibly related PRs

Suggested reviewers

  • Lee-Jong-Jin
  • taemin3
  • yangjiseonn

Poem

"나는 토끼, 코드밭을 뛰네 🐇
주문이 왔네, 길이 두 갈래로 펼쳐졌네
API와 뷰, 색깔과 칩을 심어 놓고
네비게이션 길을 깔끔히 나눴지요
당근 한 입, 새 기능 축하합니다 🥕"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed PR 제목 "SPM-175 주문관리 구현 / 주문 로직 구현"은 changeset의 주요 변경사항과 완전히 관련이 있습니다. Raw summary에서 확인할 수 있듯이, 이 PR은 Order domain의 전체 계층(Data, Domain, UI)을 구현하고 있으며, OrderAPI, OrderRepository, 다양한 use cases, OrderDetailView, OrderListView, OrderDetailViewModel 등을 추가하고 있습니다. 또한 CartListViewModel에 createOrderUseCase를 통합하여 장바구니에서 주문 생성 로직을 구현하고, 주문 상세 화면, 주문 취소, 입고 처리 등의 기능을 모두 포함하고 있습니다. 제목은 간결하고 명확하며 개발자가 PR 히스토리를 스캔할 때 주문 관리 기능 구현이 이루어졌음을 분명히 이해할 수 있습니다.
✨ 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 SPM-175

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

Copy link
Copy Markdown
Contributor

@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: 12

Caution

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

⚠️ Outside diff range comments (5)
SampoomManagement/Features/Cart/Data/Remote/API/CartAPI.swift (3)

20-27: getCartList에서 성공 여부 미검증 — 실패 응답이 빈 배열로 침묵 처리됩니다.

현재 response.success를 확인하지 않아 서버 오류/권한 문제 등이 빈 목록으로 숨겨집니다. 오류를 명시적으로 throw하도록 수정하세요.

적용 제안(diff):

   func getCartList() async throws -> [CartDto] {
     let response = try await networkManager.request(
       endpoint: "agency/1/cart",
       method: .get,
       responseType: [CartDto].self
     )
-    print("CartAPI - getCartList response: \(response)")
-    return response.data ?? []
+    guard response.success else {
+        throw NetworkError.serverError(response.status)
+    }
+    return response.data ?? []
   }

25-25: 민감 데이터 콘솔 출력 제거.

print로 전체 응답을 노출하면 PII/내부 식별자가 로그에 남습니다. 레벨/마스킹이 있는 Logger로 대체하거나 삭제하세요.

적용 제안(diff):

-        print("CartAPI - getCartList response: \(response)")

21-21: 멀티테넌트 전역 문제 — 모든 API 엔드포인트에서 agency/1 하드코딩 발견. CartAPI뿐 아니라 OrderAPI, OutboundAPI, PartAPI에서도 동일한 패턴 발견.

런타임 주입 구조로 마이그레이션이 필수입니다.

영향받는 파일:

  • CartAPI.swift: 21, 33, 46, 59, 72줄
  • OrderAPI.swift: 21, 31, 41, 50, 60줄
  • OutboundAPI.swift: 21, 33, 46, 58, 71, 84줄
  • PartAPI.swift: 37줄

권장 수정:

  • 각 API 클래스에 agencyId: Int 주입
  • 엔드포인트 구성: "agency/\(agencyId)/..."
  • 또는 중앙 라우터/API 게이트웨이에서 tenant ID 관리

QA/스테이징/프로덕션 전환 및 다중 대리점 계정 전환에서 치명적입니다.

SampoomManagement/Features/Cart/UI/CartListViewModel.swift (1)

42-56: 중복 주문 생성 가능성(재진입/다중 탭 레이스) — 즉시 차단 필요

processOrder에서 isProcessing 플래그를 Task 내부에서 설정합니다. 다중 탭 시 Task가 여러 개 생성되어 비 idempotent API(createOrder) 호출이 중복 실행될 수 있습니다. 실제 주문 중복 생성으로 이어질 수 있어 심각합니다.

다음과 같이 동기 가드 및 상태 설정을 Task 시작 전에 수행하세요.

-    private func processOrder() {
-        Task {
-            uiState = uiState.copy(isProcessing: true, processError: nil)
-            
-            do {
-                let orderList = try await createOrderUseCase.execute()
-                uiState = uiState.copy(
-                    isOrderSuccess: true,
-                    isProcessing: false,
-                    processedOrder: orderList.items
-                )
-                loadCartList() // 주문 후 장바구니 새로고침
-            } catch {
-                uiState = uiState.copy(
-                    isProcessing: false,
-                    processError: error.localizedDescription
-                )
-            }
-            print("CartListViewModel - processOrder: \(uiState)")
-        }
-    }
+    private func processOrder() {
+        // 1) 재진입 가드: 이미 처리 중이면 무시
+        guard uiState.isProcessing == false else { return }
+        // 2) 상태를 즉시 갱신하여 추가 탭을 차단
+        uiState = uiState.copy(isProcessing: true, processError: nil)
+
+        Task {
+            do {
+                let orderList = try await createOrderUseCase.execute()
+                uiState = uiState.copy(
+                    isOrderSuccess: true,
+                    isProcessing: false,
+                    processedOrder: orderList.items
+                )
+                loadCartList() // 주문 후 장바구니 새로고침
+            } catch {
+                uiState = uiState.copy(
+                    isProcessing: false,
+                    processError: error.localizedDescription
+                )
+            }
+            #if DEBUG
+            print("CartListViewModel - processOrder: \(uiState)")
+            #endif
+        }
+    }

추가로, 버튼 단에서 uiState.isProcessing로 비활성화 처리까지 연결해 이중 방어를 권장합니다.

Also applies to: 87-106

SampoomManagement/Features/Cart/UI/CartListUiState.swift (1)

62-79: processedOrder를 Optional-Optional로 변경하십시오 (processError와 일관성 유지).

dismissOrderResult에서 processedOrder: nil을 전달해도 기존 값이 유지되는 설계 결함입니다. processError는 이미 String??로 구현되어 있으므로, processedOrder도 일관성 있게 변경해야 합니다.

-        processedOrder: [Order]? = nil
+        processedOrder: [Order]?? = nil

이 변경 후 호출 시:

// CartListViewModel.swift Line 55
-            uiState = uiState.copy(isOrderSuccess: false, processedOrder: nil)
+            uiState = uiState.copy(isOrderSuccess: false, processedOrder: .some(nil))

이렇게 하면 processedOrder ?? self.processedOrder에서 .some(nil)이 명시적으로 nil을 설정하므로, 시트가 정상적으로 닫힙니다.

♻️ Duplicate comments (1)
SampoomManagement/Resources/Assets.xcassets/FailRed.colorset/Contents.json (1)

1-20: 다크 모드/대비비율 점검은 SuccessGreen 코멘트 안내를 따라 주세요.

동일한 권장사항이 적용됩니다(다크 변형, 대비 검증 스크립트).

🧹 Nitpick comments (31)
SampoomManagement/Features/Cart/Data/Remote/DTO/UpdateCartRequestDto.swift (1)

10-12: 수량 값의 유효성 정의가 필요합니다(음수/0 허용 여부).

서버 계약에 맞춰 클라이언트에서 최소한의 검증을 권장합니다. 요청 전 단계에서 0 미만을 차단하면 불필요한 왕복을 줄일 수 있습니다. 또한 디코딩이 없다면 CodableEncodable 축소가 가능합니다.

예시(권장안):

-struct UpdateCartRequestDto: Codable {
-    let quantity: Int
-}
+struct UpdateCartRequestDto: Encodable {
+    let quantity: Int
+    init(quantity: Int) {
+        precondition(quantity >= 0, "quantity must be >= 0")
+        self.quantity = quantity
+    }
+}

서버가 0을 “제거”로 해석한다면, 뷰모델 레벨에서 명시적 분기(0 → 삭제 API, 그 외 → 업데이트 API)로 처리하는지도 확인 부탁드립니다.

SampoomManagement/Features/Cart/Domain/Models/CartList.swift (1)

10-24: 파생 상태는 계산 프로퍼티로 단순화 가능.

totalCount, isEmptyitems로부터 파생됩니다. 계산 프로퍼티로 전환하면 상태 불일치 가능성을 제거하고 초기화 로직을 단순화할 수 있습니다. empty()는 정적 프로퍼티로도 표현 가능합니다.

예시(diff):

 struct CartList: Equatable {
     let items: [Cart]
-    let totalCount: Int
-    let isEmpty: Bool
+    var totalCount: Int { items.count }
+    var isEmpty: Bool { items.isEmpty }
     
     init(items: [Cart]) {
         self.items = items
-        self.totalCount = items.count
-        self.isEmpty = items.isEmpty
     }
     
-    static func empty() -> CartList {
-        return CartList(items: [])
-    }
+    static let empty = CartList(items: [])
 }

호환성 이슈가 우려되면 empty() 유지하고 계산 프로퍼티만 우선 적용해도 됩니다.

SampoomManagement/Features/Order/Domain/Models/OrderList.swift (2)

13-20: 파생 값은 계산 프로퍼티로 단일 소스 유지 권장

totalCount, isEmpty는 items에서 유도됩니다. 저장값 중복은 향후 변경 시 불일치 위험이 있습니다. 계산 프로퍼티로 단순화하세요.

-    let totalCount: Int
-    let isEmpty: Bool
+    var totalCount: Int { items.count }
+    var isEmpty: Bool { items.isEmpty }
@@
-    init(items: [Order] = []) {
-        self.items = items
-        self.totalCount = items.count
-        self.isEmpty = items.isEmpty
-    }
+    init(items: [Order] = []) {
+        self.items = items
+    }

22-25: empty() 구현 스타일을 CartList와 일관화

CartList.empty()는 명시적으로 items: []를 전달합니다. 동일한 스타일로 맞추면 가독성과 일관성이 좋아집니다.

-    static func empty() -> OrderList {
-        return OrderList()
-    }
+    static func empty() -> OrderList {
+        return OrderList(items: [])
+    }
SampoomManagement/Core/Utilities/DateFormatter.swift (1)

22-25: 날짜 파싱 로케일/타임존 보강

고정 포맷 파싱에는 en_US_POSIX와 명시적 캘린더/TZ가 권장됩니다. 출력도 의도한 TZ(기기 로컬 vs KST)를 명확히 하세요.

-        let outputFormatter = Foundation.DateFormatter()
-        outputFormatter.dateFormat = "yyyy-MM-dd"
-        outputFormatter.locale = Locale(identifier: "ko_KR")
+        let outputFormatter = Foundation.DateFormatter()
+        outputFormatter.dateFormat = "yyyy-MM-dd"
+        outputFormatter.locale = Locale(identifier: "ko_KR")
+        outputFormatter.calendar = Calendar(identifier: .gregorian)
+        outputFormatter.timeZone = TimeZone.current  // KST 고정이 필요하면 TimeZone(identifier: "Asia/Seoul")
@@
-                let formatter = Foundation.DateFormatter()
-                formatter.dateFormat = format
-                formatter.locale = Locale(identifier: "ko_KR")
+                let formatter = Foundation.DateFormatter()
+                formatter.dateFormat = format
+                formatter.locale = Locale(identifier: "en_US_POSIX")
+                formatter.calendar = Calendar(identifier: .gregorian)
+                formatter.timeZone = TimeZone(secondsFromGMT: 0)

출력 TZ를 기기 로컬로 둘지(KST 고정) 결정 필요.

Also applies to: 35-41

SampoomManagement/Features/Cart/Domain/UseCase/GetCartUseCase.swift (1)

10-10: 불필요한 동적 디스패치 제거

상속 예정 없으면 final로 표시해 비용과 오용을 줄이세요.

-class GetCartUseCase {
+final class GetCartUseCase {
SampoomManagement/Features/Cart/Domain/Repository/CartRepository.swift (1)

12-16: 수량 파라미터 계약 명시

quantity는 1 이상 제약이 필요해 보입니다. 레포지토리 계층 또는 상위 UseCase에서 검증하는지 명시/보장해주세요. 프로토콜 주석으로 계약을 고정하면 좋습니다.

 protocol CartRepository {
-    func getCartList() async throws -> CartList
-    func addCart(partId: Int, quantity: Int) async throws -> Void
+    /// quantity는 1 이상이어야 합니다.
+    func getCartList() async throws -> CartList
+    func addCart(partId: Int, quantity: Int) async throws
     func deleteCart(cartItemId: Int) async throws -> Void
     func deleteAllCart() async throws -> Void
-    func updateCartQuantity(cartItemId: Int, quantity: Int) async throws -> Void
+    /// quantity는 1 이상이어야 합니다.
+    func updateCartQuantity(cartItemId: Int, quantity: Int) async throws -> Void
 }
SampoomManagement/Features/Cart/Domain/UseCase/DeleteCartUseCase.swift (1)

10-10: final 지정으로 의도 명확화

상속 필요 없으면 final 권장.

-class DeleteCartUseCase {
+final class DeleteCartUseCase {
SampoomManagement/Features/Cart/Data/Repository/CartRepositoryImpl.swift (2)

10-10: 레포지토리 구현 클래스도 final 권장

상속 계획 없으면 final로 고정.

-class CartRepositoryImpl: CartRepository {
+final class CartRepositoryImpl: CartRepository {

11-15: API 의존성은 프로토콜로 주입해 테스트 용이성 확보

현재 CartAPI 구상 타입에 직접 의존합니다. CartAPIProtocol을 도입해 목 주입/스텁 테스트가 쉬워지도록 하세요.

SampoomManagement/Features/Cart/Domain/UseCase/DeleteAllCartUseCase.swift (1)

10-10: final 지정으로 경량화

상속 불필요 시 final 권장.

-class DeleteAllCartUseCase {
+final class DeleteAllCartUseCase {
SampoomManagement/Features/Order/UI/OrderDetailUiEvent.swift (1)

8-16: UI 약어 표기와 불필요한 import 정리 제안

  • 타입명: Swift 관례상 UI 약어는 대문자 표기(…UIEvent)가 가독성이 좋습니다.
  • Foundation는 미사용이므로 제거 가능.

가능한 최소 수정(diff):

-import Foundation
+// Foundation 미사용

타입명 변경은 파급이 커서 이번 PR에서는 유지해도 무방하나, 후속 리팩터링 시 OrderDetailUIEvent로의 일괄 변경을 권장합니다.

SampoomManagement/Features/Order/Domain/UseCase/GetOrderDetailUseCase.swift (1)

10-20: final 지정으로 비용 절감 및 의도 명확화

상속 필요 없어 보입니다. final class로 선언해 동적 디스패치 비용을 줄이고 의도를 명확히 하세요.

-class GetOrderDetailUseCase {
+final class GetOrderDetailUseCase {
SampoomManagement/Features/Order/Domain/UseCase/CreateOrderUseCase.swift (1)

10-20: 일관성 있게 final 적용 권장

다른 유스케이스와 함께 final을 적용해 일관성과 성능을 확보하세요.

-class CreateOrderUseCase {
+final class CreateOrderUseCase {
SampoomManagement/Features/Order/Domain/UseCase/GetOrderUseCase.swift (1)

10-20: final 적용으로 통일성/성능 개선

상속이 필요 없다면 final을 적용하세요.

-class GetOrderUseCase {
+final class GetOrderUseCase {
SampoomManagement/Features/Order/Domain/UseCase/CancelOrderUseCase.swift (1)

10-20: 간결하고 명확합니다. final로 마무리하면 더 좋습니다

구현은 적절합니다. 상속이 필요 없으므로 final 지정으로 통일성을 맞추세요.

-class CancelOrderUseCase {
+final class CancelOrderUseCase {
SampoomManagement/Features/Order/UI/OrderListUiState.swift (1)

10-36: 상태 구조체에 Equatable/Sendable 컨포먼스 고려

SwiftUI 리렌더 효율과 동시성 안전성을 위해(모델이 지원한다면) 아래 컨포먼스를 권장합니다.

-struct OrderListUiState {
+struct OrderListUiState: Equatable, Sendable {

참고: OrderEquatable/Sendable을 만족해야 합니다. 미충족 시 먼저 Order 측 컨포먼스를 추가해 주세요.

SampoomManagement/Features/Order/Domain/Models/OrderStatus.swift (1)

15-28: from() 메서드를 단순화할 수 있습니다.

현재 구현은 정상 작동하지만, Swift의 내장 init?(rawValue:)를 활용하면 더 간결하게 작성할 수 있습니다. switch 문이 enum의 raw value 매핑을 중복하고 있습니다.

다음과 같이 단순화할 수 있습니다:

     static func from(_ rawValue: String?) -> OrderStatus {
-        guard let rawValue = rawValue?.uppercased() else { return .pending }
-        
-        switch rawValue {
-        case "PENDING":
-            return .pending
-        case "COMPLETED":
-            return .completed
-        case "CANCELED":
-            return .canceled
-        default:
-            return .pending
-        }
+        return OrderStatus(rawValue: rawValue?.uppercased() ?? "") ?? .pending
     }
SampoomManagement/Core/Utilities/OrderFormatter.swift (1)

26-26: 강제 언래핑 대신 안전한 옵셔널 처리를 권장합니다.

Line 22의 guard로 인해 안전하지만, 강제 언래핑(!)은 일반적으로 지양하는 것이 좋습니다.

다음과 같이 개선할 수 있습니다:

-        let first = flattened.first!
+        guard let first = flattened.first else {
+            return "-"
+        }
         let groupName = first.group
SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (3)

9-9: 불필요한 import 제거.

Combine을 사용하지 않습니다. 정리해 주세요.

-import Combine

40-41: 색상 지정 일관성.

Color(.failRed)UIColor 확장을 전제합니다. 프로젝트 전반에서 자산 색상을 Color("...")로 쓰는 패턴과 맞추거나, 표준 .red를 사용하세요. 런타임/컴파일 리스크를 줄입니다.

자산 또는 UIColor 확장에 failRed가 실제로 존재하는지 확인해 주세요.


86-106: 내부 전용 뷰는 접근 제어를 제한하세요.

파일 내부 전용 헤더라면 private로 두는 것이 좋습니다.

-struct OrderCompleteHeader: View {
+private struct OrderCompleteHeader: View {
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (2)

11-16: 상속 불필요 시 final로 봉인하여 비용과 오용을 줄이세요.

-class OrderAPI {
+final class OrderAPI {

18-26: 하드코딩된 agencyId(1) 제거 권장.

향후 멀티 에이전시 지원을 위해 agencyId를 파라미터화하거나 DI로 전달하는 것이 안전합니다.

Also applies to: 28-36, 47-55

SampoomManagement/Features/Cart/UI/CartListView.swift (4)

53-57: View 내부에서 네트워크/리포지토리/뷰모델을 직접 생성 (DI 역행).

시트가 열릴 때마다 NetworkManager → OrderAPI → OrderRepositoryImpl → OrderDetailViewModel이 새로 생성됩니다. 세션/인터셉터/환경설정이 분기되고 테스트가 어려워집니다. DI 컨테이너(예: AppDependencies)에서 팩토리/주입받는 형태로 바꾸세요.

예: CartListView에 팩토리 클로저 주입

-struct CartListView: View {
+struct CartListView: View {
     @ObservedObject var viewModel: CartListViewModel
+    let makeOrderDetailViewModel: () -> OrderDetailViewModel
@@
-                                viewModel: OrderDetailViewModel(
-                                    getOrderDetailUseCase: GetOrderDetailUseCase(repository: OrderRepositoryImpl(api: OrderAPI(networkManager: NetworkManager()))),
-                                    cancelOrderUseCase: CancelOrderUseCase(repository: OrderRepositoryImpl(api: OrderAPI(networkManager: NetworkManager()))),
-                                    receiveOrderUseCase: ReceiveOrderUseCase(repository: OrderRepositoryImpl(api: OrderAPI(networkManager: NetworkManager())))
-                                )
+                                viewModel: makeOrderDetailViewModel()

16-30: 읽기 전용 Binding 생성은 과합니다.

단순 관찰용이면 Bool 값 자체를 넘기고, 필요 시 상위에서 .onChange로 처리하는 편이 간단합니다. 불필요한 Binding 박싱을 줄여 렌더링/타입체킹 부담을 낮출 수 있습니다.


9-9: 불필요한 import 제거.

Combine을 직접 사용하지 않습니다.

-import Combine

137-146: 주문 처리 중 버튼 비활성화/로딩 피드백 권장.

uiState.isProcessing 동안 orderButton을 비활성화하고 스피너/ProgressView를 표시하면 중복 요청을 방지하고 UX를 개선할 수 있습니다.

SampoomManagement/Features/Order/UI/OrderDetailView.swift (2)

111-111: TODO 주석을 해결하거나 이슈로 추적하세요.

"Toast 대신 Alert 사용하거나 다른 방법으로 처리" 주석은 구현이 완료되지 않았거나 임시 해결책임을 시사합니다.

성공/에러 피드백을 어떻게 처리할지 결정하고 주석을 제거하거나, 별도 이슈로 추적이 필요하면 알려주세요.

Also applies to: 118-118


131-134: 방어 로직이 명확하지만 개선 가능합니다.

orderDetail.first가 nil일 때 true를 반환하는 것은 안전하지만, 의도를 더 명확하게 표현할 수 있습니다.

다음과 같이 리팩토링을 고려해보세요:

 private var cannotPerformAction: Bool {
-    guard let order = viewModel.uiState.orderDetail.first else { return true }
-    return order.status == .completed || order.status == .canceled
+    guard let order = viewModel.uiState.orderDetail.first else { 
+        return true // 주문 정보가 없으면 작업 불가
+    }
+    return order.status == .completed || order.status == .canceled
 }
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (1)

20-20: orderId 초기화 방식을 명확히 하세요.

orderId가 생성자에서 설정되고 별도의 setOrderId 메서드도 있어 혼란스럽습니다. orderId의 기본값 0도 유효한 주문 ID가 아닐 수 있습니다.

두 가지 접근 방식을 권장합니다:

  1. 생성자 전용: setOrderId 메서드를 제거하고 생성자에서만 설정 (기본값 제거)
  2. 지연 설정: 생성자에서 orderId를 optional로 변경하고 setOrderId로만 설정

현재 코드베이스에서 AppDependencies.makeOrderDetailViewModel(orderId:)가 생성 시 orderId를 전달하므로, 첫 번째 접근이 더 적합해 보입니다.

-private var orderId: Int = 0
+private let orderId: Int

 init(
     getOrderDetailUseCase: GetOrderDetailUseCase,
     cancelOrderUseCase: CancelOrderUseCase,
     receiveOrderUseCase: ReceiveOrderUseCase,
-    orderId: Int = 0
+    orderId: Int
 ) {
     self.getOrderDetailUseCase = getOrderDetailUseCase
     self.cancelOrderUseCase = cancelOrderUseCase
     self.receiveOrderUseCase = receiveOrderUseCase
     self.orderId = orderId
 }
-
-func setOrderId(_ orderId: Int) {
-    print("OrderDetailViewModel - SETorderId : \(orderId)")
-    self.orderId = orderId
-}

Also applies to: 26-26, 34-37

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10099d4 and e986baf.

📒 Files selected for processing (52)
  • SampoomManagement/App/ContentView.swift (4 hunks)
  • SampoomManagement/Core/DI/AppDependencies.swift (3 hunks)
  • SampoomManagement/Core/Network/NetworkManager.swift (0 hunks)
  • SampoomManagement/Core/Resources/StringResources.swift (1 hunks)
  • SampoomManagement/Core/UI/Components/StatusChip.swift (1 hunks)
  • SampoomManagement/Core/Utilities/DateFormatter.swift (1 hunks)
  • SampoomManagement/Core/Utilities/OrderFormatter.swift (1 hunks)
  • SampoomManagement/Features/Cart/Data/Mappers/CartMappers.swift (1 hunks)
  • SampoomManagement/Features/Cart/Data/Remote/API/CartAPI.swift (1 hunks)
  • SampoomManagement/Features/Cart/Data/Remote/DTO/AddCartRequestDto.swift (1 hunks)
  • SampoomManagement/Features/Cart/Data/Remote/DTO/CartDto.swift (1 hunks)
  • SampoomManagement/Features/Cart/Data/Remote/DTO/UpdateCartRequestDto.swift (1 hunks)
  • SampoomManagement/Features/Cart/Data/Repository/CartRepositoryImpl.swift (1 hunks)
  • SampoomManagement/Features/Cart/Domain/Models/Cart.swift (1 hunks)
  • SampoomManagement/Features/Cart/Domain/Models/CartList.swift (1 hunks)
  • SampoomManagement/Features/Cart/Domain/Repository/CartRepository.swift (1 hunks)
  • SampoomManagement/Features/Cart/Domain/UseCase/AddCartUseCase.swift (1 hunks)
  • SampoomManagement/Features/Cart/Domain/UseCase/DeleteAllCartUseCase.swift (1 hunks)
  • SampoomManagement/Features/Cart/Domain/UseCase/DeleteCartUseCase.swift (1 hunks)
  • SampoomManagement/Features/Cart/Domain/UseCase/GetCartUseCase.swift (1 hunks)
  • SampoomManagement/Features/Cart/Domain/UseCase/UpdateCartQuantityUseCase.swift (1 hunks)
  • SampoomManagement/Features/Cart/UI/CartListUiEvent.swift (2 hunks)
  • SampoomManagement/Features/Cart/UI/CartListUiState.swift (6 hunks)
  • SampoomManagement/Features/Cart/UI/CartListView.swift (1 hunks)
  • SampoomManagement/Features/Cart/UI/CartListViewModel.swift (4 hunks)
  • SampoomManagement/Features/Order/Data/Mappers/OrderMappers .swift (1 hunks)
  • SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1 hunks)
  • SampoomManagement/Features/Order/Data/Remote/DTO/OrderDto.swift (1 hunks)
  • SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1 hunks)
  • SampoomManagement/Features/Order/Domain/Models/Order.swift (1 hunks)
  • SampoomManagement/Features/Order/Domain/Models/OrderList.swift (1 hunks)
  • SampoomManagement/Features/Order/Domain/Models/OrderStatus.swift (1 hunks)
  • SampoomManagement/Features/Order/Domain/Repository/OrderRepository.swift (1 hunks)
  • SampoomManagement/Features/Order/Domain/UseCase/CancelOrderUseCase.swift (1 hunks)
  • SampoomManagement/Features/Order/Domain/UseCase/CreateOrderUseCase.swift (1 hunks)
  • SampoomManagement/Features/Order/Domain/UseCase/GetOrderDetailUseCase.swift (1 hunks)
  • SampoomManagement/Features/Order/Domain/UseCase/GetOrderUseCase.swift (1 hunks)
  • SampoomManagement/Features/Order/Domain/UseCase/ReceiveOrderUseCase.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderDetailContent.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderDetailUiEvent.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderDetailUiState.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderDetailView.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderListUiEvent.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderListUiState.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderListView.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderListViewModel.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (1 hunks)
  • SampoomManagement/Features/Part/UI/PartDetailBottomSheetView.swift (1 hunks)
  • SampoomManagement/Resources/Assets.xcassets/FailRed.colorset/Contents.json (1 hunks)
  • SampoomManagement/Resources/Assets.xcassets/SuccessGreen.colorset/Contents.json (1 hunks)
  • SampoomManagement/Resources/Assets.xcassets/WaitYellow.colorset/Contents.json (1 hunks)
💤 Files with no reviewable changes (1)
  • SampoomManagement/Core/Network/NetworkManager.swift
🧰 Additional context used
🧬 Code graph analysis (23)
SampoomManagement/Features/Order/UI/OrderDetailUiEvent.swift (3)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (2)
  • receiveOrder (39-45)
  • cancelOrder (58-64)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (2)
  • receiveOrder (29-31)
  • cancelOrder (39-41)
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (2)
  • receiveOrder (102-121)
  • cancelOrder (80-100)
SampoomManagement/Features/Order/Domain/UseCase/CreateOrderUseCase.swift (2)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1)
  • createOrder (29-36)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1)
  • createOrder (23-27)
SampoomManagement/Features/Order/Domain/UseCase/GetOrderDetailUseCase.swift (2)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1)
  • getOrderDetail (48-55)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1)
  • getOrderDetail (33-37)
SampoomManagement/Features/Order/Domain/UseCase/ReceiveOrderUseCase.swift (3)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1)
  • receiveOrder (39-45)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1)
  • receiveOrder (29-31)
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (1)
  • receiveOrder (102-121)
SampoomManagement/Features/Order/Domain/UseCase/GetOrderUseCase.swift (3)
SampoomManagement/Features/Order/Domain/UseCase/CreateOrderUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1)
  • getOrderList (19-26)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1)
  • getOrderList (17-21)
SampoomManagement/Features/Order/UI/OrderListUiEvent.swift (1)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (1)
  • loadOrderList (29-48)
SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (3)
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (4)
  • setOrderId (34-37)
  • onEvent (39-50)
  • cancelOrder (80-100)
  • clearSuccess (52-57)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1)
  • cancelOrder (58-64)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1)
  • cancelOrder (39-41)
SampoomManagement/Features/Order/Data/Mappers/OrderMappers .swift (1)
SampoomManagement/Features/Cart/Data/Mappers/CartMappers.swift (3)
  • toModel (11-13)
  • toModel (17-19)
  • toModel (23-25)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (3)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (5)
  • getOrderList (19-26)
  • createOrder (29-36)
  • receiveOrder (39-45)
  • getOrderDetail (48-55)
  • cancelOrder (58-64)
SampoomManagement/Features/Order/Data/Mappers/OrderMappers .swift (4)
  • toModel (11-13)
  • toModel (17-19)
  • toModel (23-25)
  • toModel (29-31)
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (2)
  • receiveOrder (102-121)
  • cancelOrder (80-100)
SampoomManagement/Features/Cart/UI/CartListViewModel.swift (2)
SampoomManagement/Features/Cart/UI/CartListUiState.swift (1)
  • copy (52-80)
SampoomManagement/Features/Order/Domain/UseCase/CreateOrderUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Order/UI/OrderListView.swift (3)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (2)
  • onEvent (22-27)
  • loadOrderList (29-48)
SampoomManagement/Core/Utilities/OrderFormatter.swift (1)
  • buildOrderTitle (11-38)
SampoomManagement/Core/Utilities/DateFormatter.swift (1)
  • formatDate (16-50)
SampoomManagement/Features/Order/Domain/UseCase/CancelOrderUseCase.swift (4)
SampoomManagement/Features/Order/Domain/UseCase/ReceiveOrderUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1)
  • cancelOrder (58-64)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1)
  • cancelOrder (39-41)
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (1)
  • cancelOrder (80-100)
SampoomManagement/Features/Order/UI/OrderListUiState.swift (2)
SampoomManagement/Features/Cart/UI/CartListUiState.swift (1)
  • copy (52-80)
SampoomManagement/Features/Order/UI/OrderDetailUiState.swift (1)
  • copy (37-55)
SampoomManagement/Features/Order/Domain/Models/OrderList.swift (1)
SampoomManagement/Features/Cart/Domain/Models/CartList.swift (1)
  • empty (21-23)
SampoomManagement/Features/Order/UI/OrderDetailContent.swift (1)
SampoomManagement/Core/Utilities/DateFormatter.swift (1)
  • formatDate (16-50)
SampoomManagement/Features/Cart/UI/CartListView.swift (3)
SampoomManagement/Features/Cart/UI/CartListViewModel.swift (4)
  • onEvent (36-57)
  • deleteAllCart (217-245)
  • processOrder (87-107)
  • loadCartList (59-85)
SampoomManagement/Features/Cart/Data/Remote/API/CartAPI.swift (1)
  • deleteAllCart (70-79)
SampoomManagement/Features/Cart/Data/Repository/CartRepositoryImpl.swift (1)
  • deleteAllCart (32-34)
SampoomManagement/Features/Order/Domain/Repository/OrderRepository.swift (2)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (5)
  • getOrderList (19-26)
  • createOrder (29-36)
  • receiveOrder (39-45)
  • getOrderDetail (48-55)
  • cancelOrder (58-64)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (5)
  • getOrderList (17-21)
  • createOrder (23-27)
  • receiveOrder (29-31)
  • getOrderDetail (33-37)
  • cancelOrder (39-41)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (2)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (5)
  • getOrderList (17-21)
  • createOrder (23-27)
  • receiveOrder (29-31)
  • getOrderDetail (33-37)
  • cancelOrder (39-41)
SampoomManagement/Core/Network/NetworkManager.swift (1)
  • request (18-56)
SampoomManagement/Features/Order/UI/OrderDetailUiState.swift (2)
SampoomManagement/Features/Cart/UI/CartListUiState.swift (1)
  • copy (52-80)
SampoomManagement/Features/Order/UI/OrderListUiState.swift (1)
  • copy (25-35)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (2)
SampoomManagement/Features/Order/UI/OrderListUiState.swift (1)
  • copy (25-35)
SampoomManagement/Features/Order/Domain/UseCase/GetOrderUseCase.swift (1)
  • execute (17-19)
SampoomManagement/App/ContentView.swift (1)
SampoomManagement/Core/DI/AppDependencies.swift (2)
  • makeOrderListViewModel (155-157)
  • makeOrderDetailViewModel (159-166)
SampoomManagement/Features/Order/UI/OrderDetailView.swift (3)
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (4)
  • onEvent (39-50)
  • cancelOrder (80-100)
  • receiveOrder (102-121)
  • clearSuccess (52-57)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (2)
  • cancelOrder (58-64)
  • receiveOrder (39-45)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (2)
  • cancelOrder (39-41)
  • receiveOrder (29-31)
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (6)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (2)
  • receiveOrder (39-45)
  • cancelOrder (58-64)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (2)
  • receiveOrder (29-31)
  • cancelOrder (39-41)
SampoomManagement/Features/Order/UI/OrderDetailUiState.swift (1)
  • copy (37-55)
SampoomManagement/Features/Order/Domain/UseCase/CancelOrderUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Order/Domain/UseCase/GetOrderDetailUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Order/Domain/UseCase/ReceiveOrderUseCase.swift (1)
  • execute (17-19)
🔇 Additional comments (31)
SampoomManagement/Features/Cart/Data/Mappers/CartMappers.swift (2)

5-5: 작성자 정보 업데이트 - 승인

파일 헤더의 작성자 이름 업데이트가 적절하게 반영되었습니다.


10-26: 매퍼 로직 - 승인

DTO에서 도메인 모델로의 변환 로직이 명확하고 정확합니다. 각 매퍼에서:

  • CartDtoCart: categoryId, categoryName, groups 그룹화 및 변환
  • CartGroupDtoCartGroup: groupId, groupName, parts 그룹화 및 변환
  • CartPartDtoCartPart: cartItemId, partId, code, name, quantity 필드 매핑

재귀적 toModel() 호출을 통해 중첩된 객체 구조가 일관되게 변환됩니다.

SampoomManagement/Features/Cart/Domain/Models/Cart.swift (1)

5-5: 헤더 작성자 정보 업데이트 - 승인됨

파일 헤더의 작성자 정보만 업데이트되었습니다. Cart 도메인 모델은 구조가 명확하고 Equatable 프로토콜로 비교 가능하며 불변성이 보장됩니다.

SampoomManagement/Features/Cart/Data/Remote/DTO/CartDto.swift (1)

5-5: 메타데이터만 변경되었으므로 승인합니다.

라인 5의 작성자 정보 업데이트는 적절합니다. 다만 이 파일이 SPM-175 주문관리 구현 PR의 주요 변경사항과 어떻게 연결되는지 확인해주시기 바랍니다. Cart DTO 자체는 변경되지 않았으므로, 이 메타데이터 업데이트가 주문 기능 구현에 필수적인지 검토해주시면 좋겠습니다.

SampoomManagement/Features/Cart/Data/Remote/DTO/AddCartRequestDto.swift (1)

5-5: 메타데이터 업데이트 확인.

작성자 정보를 업데이트한 메타데이터 변경으로, 코드 기능에는 영향이 없습니다.

SampoomManagement/Features/Order/Domain/Models/OrderList.swift (1)

11-14: 접근 제어 확인

struct OrderList가 내부 모듈 경계를 넘나들어 사용된다면 public 또는 at least internal 그대로인지 확인 바랍니다. 외부 모듈에서 사용할 계획이라면 접근 제어 지정이 필요할 수 있습니다.

SampoomManagement/Features/Cart/Data/Repository/CartRepositoryImpl.swift (1)

23-26: quantity 유효성 검증 책임 확인

서버/UseCase에서 1 이상 보장되는지 확인 바랍니다. 레포지토리 레벨에서 방어적 체크를 두지 않는다면 상위 레이어에서 확실히 검증해 주세요.

Also applies to: 36-39

SampoomManagement/Features/Cart/Domain/UseCase/UpdateCartQuantityUseCase.swift (1)

5-5: LGTM! 작성자 정보 업데이트 확인

메타데이터만 변경되었으며, 로직에는 영향이 없습니다.

SampoomManagement/Features/Part/UI/PartDetailBottomSheetView.swift (1)

201-201: LGTM! UI 일관성 개선

장바구니 버튼의 색상을 .blue에서 .accent로 변경하여 앱 전체의 색상 일관성을 향상시켰습니다.

SampoomManagement/Features/Cart/Domain/UseCase/AddCartUseCase.swift (1)

5-5: LGTM! 작성자 정보 업데이트 확인

메타데이터만 변경되었으며, 로직에는 영향이 없습니다.

SampoomManagement/Resources/Assets.xcassets/WaitYellow.colorset/Contents.json (1)

1-20: LGTM! 상태 표시 색상 추가

대기(pending) 상태를 나타내는 WaitYellow 색상 에셋이 올바르게 정의되었습니다. SuccessGreen, FailRed와 함께 일관된 상태 표시 색상 시스템을 구성합니다.

SampoomManagement/Features/Cart/UI/CartListUiEvent.swift (1)

19-19: LGTM! 주문 결과 해제 이벤트 추가

장바구니에서 주문 생성 후 결과 화면을 닫기 위한 dismissOrderResult 이벤트가 적절하게 추가되었습니다.

SampoomManagement/Features/Order/UI/OrderListUiEvent.swift (1)

10-13: LGTM! 주문 목록 UI 이벤트 정의

주문 목록 화면의 UI 이벤트가 명확하게 정의되었습니다. 기존 다른 기능들(Cart, Part)과 일관된 패턴을 따르고 있습니다.

SampoomManagement/Features/Order/Domain/UseCase/ReceiveOrderUseCase.swift (1)

10-20: LGTM! 입고 처리 유스케이스 구현

주문 입고 처리를 위한 유스케이스가 올바르게 구현되었습니다. 기존의 다른 유스케이스들과 일관된 패턴을 따르며, OrderDetailViewModel 및 Repository 계층과 적절히 통합되어 있습니다.

SampoomManagement/Core/Resources/StringResources.swift (1)

126-147: 주문 관련 문자열 리소스 추가 확인

주문 관리 기능에 필요한 문자열 상수들이 체계적으로 정리되어 추가되었습니다. 기존의 다른 기능들(Cart, Part, Outbound)과 일관된 구조를 따르고 있습니다.

SampoomManagement/Core/UI/Components/StatusChip.swift (1)

18-25: 접근성(명암비) 이슈: 텍스트/배경 동일 계열 색 사용

현재 텍스트와 배경이 동일 색상의 변형으로 설정되어 있습니다. 작은 캡션 폰트에서 WCAG 명암비(4.5:1) 미달 가능성이 있으며, 라이트/다크 모드 환경에서 가독성 저하가 발생할 수 있습니다.

권장 수정(텍스트는 시스템 기본색, 배경은 브랜드 색 틴트 + 외곽선):

-        Text(text)
-            .font(.caption)
-            .foregroundColor(color)
-            .padding(.horizontal, 12)
-            .padding(.vertical, 6)
-            .background(color.opacity(0.2))
-            .cornerRadius(16)
+        Text(text)
+            .font(.caption)
+            .foregroundColor(.primary)
+            .padding(.horizontal, 12)
+            .padding(.vertical, 6)
+            .background(color.opacity(0.15))
+            .overlay(
+                RoundedRectangle(cornerRadius: 16)
+                    .stroke(color, lineWidth: 1)
+            )
+            .cornerRadius(16)

추가 제안:

  • 상태 타입을 String 대신 도메인 enum(예: OrderStatus)으로 받아 매핑 실수를 방지하세요.
  • 토큰 비교 시 로케일 비의존 처리를 권장합니다:
- switch status.lowercased() {
+ switch status.lowercased(with: Locale(identifier: "en_US_POSIX")) {
  • 한국어 레이블은 Localizable.strings로 이동하여 다국어 확장에 대비하세요.

수동 검증 필요:
색상 심볼(Color(.waitYellow), Color(.successGreen), Color(.failRed)) 정의가 프로젝트 내에 존재하는지 확인하세요. UIColor 확장이나 Asset Catalog Color Set으로 정의되어야 합니다. 또한 다른 파일에서 이 색상들을 올바르게 참조하는지 컴파일 상태를 확인하세요.

Also applies to: 27-37

SampoomManagement/Features/Order/Domain/Models/Order.swift (1)

10-36: LGTM! 잘 구조화된 도메인 모델입니다.

주문 관련 데이터 모델이 계층적으로 잘 설계되었으며, 불변성과 Equatable 준수가 적절합니다.

SampoomManagement/Features/Order/Domain/Repository/OrderRepository.swift (1)

10-16: LGTM! 깔끔한 저장소 프로토콜 정의입니다.

비동기 작업에 대한 적절한 async/throws 사용과 명확한 메서드 시그니처가 잘 정의되어 있습니다.

SampoomManagement/Features/Order/UI/OrderListView.swift (1)

11-72: LGTM! 잘 구조화된 리스트 뷰입니다.

로딩, 에러, 빈 상태, 리스트 상태를 모두 적절하게 처리하고 있으며, LazyVStack을 사용한 성능 최적화도 잘 되어 있습니다.

SampoomManagement/Features/Order/UI/OrderDetailContent.swift (1)

11-154: LGTM! 깔끔한 컴포넌트 구조입니다.

주문 상세 정보를 잘 계층화된 컴포넌트로 분리하여 가독성과 유지보수성이 우수합니다. 날짜 포맷팅과 상태 표시도 적절하게 처리되었습니다.

SampoomManagement/Features/Order/Data/Mappers/OrderMappers .swift (1)

10-32: LGTM! 일관성 있는 매퍼 구현입니다.

DTO에서 도메인 모델로의 변환이 깔끔하게 구현되었으며, 기존 CartMappers 패턴과 일관성을 유지하고 있습니다.

SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1)

20-23: 엔드포인트 선행 슬래시 혼용을 정리하세요.

CartAPI"agency/1/cart/clear"(슬래시 없음), 본 파일은 "/agency/1/orders"(슬래시 있음)입니다. baseURL의 트레일링 슬래시 여부와 조합에 따라 // 혹은 누락이 발생할 수 있습니다. 한쪽으로 통일하거나 라우터/빌더로 생성하세요.

NetworkManager.baseURL의 트레일링 슬래시 존재 여부를 확인해 주세요.

Also applies to: 30-33, 41-43, 50-52, 60-62

SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (2)

72-77: iOS 17 전용 onChange 시그니처 사용 위험 (하위 호환 필요).

동일하게 1‑파라미터 버전으로 수정하세요.

-        .onChange(of: viewModel.uiState.isProcessingError) { _, newValue in
+        .onChange(of: viewModel.uiState.isProcessingError) { newValue in
             if newValue != nil {
                 // Toast 대신 Alert 사용하거나 다른 방법으로 처리
                 viewModel.onEvent(.clearError)
             }
         }
⛔ Skipped due to learnings
Learnt from: Sangyoon98
PR: 33-Auto/Sampoom-Management-iOS#7
File: SampoomManagement/Core/UI/Components/CommonTextField.swift:117-119
Timestamp: 2025-10-11T12:58:56.474Z
Learning: In Swift/SwiftUI iOS 17.0+, the onChange(of:perform:) modifier expects a two-parameter closure (oldValue, newValue) or zero-parameter closure, not a single-parameter closure. The older single-parameter version was deprecated in iOS 17.0.

65-71: 원본 리뷰 코멘트는 부정확합니다.

프로젝트의 최소 배포 타깃은 18.6으로, iOS 17.0 이상입니다. 따라서 onChange(of:_:) 2-파라미터 클로저 버전({ _, newValue in })은 완전히 호환되며, 코드 변경이 필요하지 않습니다.

현재 코드는 올바른 상태입니다.

SampoomManagement/Features/Cart/UI/CartListView.swift (1)

209-223: 리뷰 의견이 부정확합니다. 현재 코드는 이미 올바른 상태입니다.

프로젝트의 실제 배포 타깃은 iOS 18.6(또는 일부 구성에서 26.0)입니다. 현재 코드의 2파라미터 onChange 문법 { _, newValue in }iOS 17.0 이상에서 요구되는 정확한 시그니처이며, 프로젝트의 iOS 18.6 타깃에 완벽하게 맞습니다.

리뷰 의견은 1파라미터로 변경하도록 제안하는데, 이는 오히려 iOS 18.6+ 호환성을 깨뜨릴 수 있습니다. 하위 호환성을 위해 1파라미터 문법으로 변경할 필요는 없습니다.

Likely an incorrect or invalid review comment.

SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1)

10-41: 깔끔한 Repository 구현입니다.

API 호출을 적절히 위임하고 DTO를 도메인 모델로 매핑하는 로직이 명확합니다. 각 메서드가 단일 책임을 잘 수행하고 있습니다.

SampoomManagement/Features/Order/Data/Remote/DTO/OrderDto.swift (1)

11-37: 명확한 DTO 구조입니다.

계층적인 구조(Order > Category > Group > Part)가 잘 정의되어 있고, Codable 준수로 직렬화가 자동으로 처리됩니다.

SampoomManagement/Features/Order/UI/OrderDetailUiState.swift (1)

10-56: UI 상태 관리가 잘 설계되었습니다.

불변성을 유지하면서 상태를 업데이트할 수 있는 copy 메서드와 모든 필요한 상태 플래그들이 적절히 정의되어 있습니다. 이중 옵셔널 패턴(String??)도 기존 코드베이스와 일관성 있게 사용되고 있습니다.

SampoomManagement/Core/DI/AppDependencies.swift (3)

49-56: Order 도메인 의존성 주입이 올바르게 구성되었습니다.

API, 저장소, 유스케이스의 와이어링이 기존 패턴과 일관성 있게 구현되어 있습니다.

Also applies to: 98-105


150-152: 장바구니-주문 연동이 적절히 추가되었습니다.

CartListViewModelcreateOrderUseCase를 추가하여 장바구니에서 주문 생성 플로우를 지원합니다.


155-166: Order ViewModel 팩토리가 올바르게 구현되었습니다.

필요한 유스케이스들이 각 ViewModel에 정확히 주입되고 있습니다.

Comment thread SampoomManagement/App/ContentView.swift Outdated
Comment thread SampoomManagement/Core/Resources/StringResources.swift Outdated
Comment thread SampoomManagement/Core/Utilities/DateFormatter.swift Outdated
Comment thread SampoomManagement/Features/Cart/UI/CartListView.swift
Comment thread SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift
Comment thread SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift
Comment thread SampoomManagement/Features/Order/UI/OrderListView.swift Outdated
Comment thread SampoomManagement/Features/Order/UI/OrderListViewModel.swift
Comment thread SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift
Copy link
Copy Markdown

@vivivim vivivim left a comment

Choose a reason for hiding this comment

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

지금은 임시인 것 같지만 주문 확인은 주문 서버로 ..

Copy link
Copy Markdown
Member

@CHOOSLA CHOOSLA left a comment

Choose a reason for hiding this comment

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

확인했습니다!

@33Auto-Bot 33Auto-Bot added the ready-to-merge 3명 이상의 리뷰어에게 승인되어 병합 준비가 완료된 PR label Oct 22, 2025
Copy link
Copy Markdown
Contributor

@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: 4

♻️ Duplicate comments (1)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (1)

52-52: print 문이 여전히 MainActor 컨텍스트 외부에 있습니다.

이전 리뷰에서 지적된 것처럼 uiState에 접근하는 print 문도 MainActor 컨텍스트에서 실행되어야 합니다.

다음과 같이 수정하세요:

             }
-            print("OrderListViewModel - loadOrderList: \(uiState)")
+            await MainActor.run { 
+                print("OrderListViewModel - loadOrderList: \(uiState)")
+            }
         }
🧹 Nitpick comments (4)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (1)

29-54: Task 취소 처리를 고려해보세요.

현재 구현은 Task를 저장하거나 취소하지 않습니다. ViewModel이 해제될 때 진행 중인 Task가 자동으로 취소되지만, 명시적인 취소 처리를 추가하면 더 안전합니다.

예를 들어:

private var loadTask: Task<Void, Never>?

private func loadOrderList() {
    loadTask?.cancel()
    loadTask = Task {
        // ... 기존 코드 ...
    }
}

deinit {
    loadTask?.cancel()
}
SampoomManagement/Core/Utilities/DateFormatterUtil.swift (1)

28-43: 성능 개선: 루프 내에서 DateFormatter 인스턴스를 반복 생성하지 마세요.

DateFormatter 인스턴스는 생성 비용이 높습니다. 루프 내에서 매번 새로운 인스턴스를 생성하는 대신, 사전에 생성하고 재사용하는 것이 좋습니다. 또한 ISO 8601 날짜는 로케일 독립적이므로 라인 38의 locale 설정은 불필요하며 오히려 파싱 문제를 일으킬 수 있습니다.

다음 diff를 적용하여 개선하세요:

             // ISO 8601 파싱 실패 시 다른 형식 시도
             let alternativeFormatters = [
-                "yyyy-MM-dd'T'HH:mm:ss",
-                "yyyy-MM-dd'T'HH:mm:ss.SSS",
-                "yyyy-MM-dd'T'HH:mm:ss'Z'",
-                "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
+                ("yyyy-MM-dd'T'HH:mm:ss", false),
+                ("yyyy-MM-dd'T'HH:mm:ss.SSS", false),
+                ("yyyy-MM-dd'T'HH:mm:ss'Z'", false),
+                ("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", false)
             ]
             
-            for format in alternativeFormatters {
+            let altFormatter = Foundation.DateFormatter()
+            altFormatter.timeZone = TimeZone(secondsFromGMT: 0)
+            
+            for (format, _) in alternativeFormatters {
-                let formatter = Foundation.DateFormatter()
-                formatter.dateFormat = format
-                formatter.locale = Locale(identifier: "ko_KR")
+                altFormatter.dateFormat = format
                 
-                if let date = formatter.date(from: dateString) {
+                if let date = altFormatter.date(from: dateString) {
                     return outputFormatter.string(from: date)
                 }
             }
SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (1)

36-44: 취소 버튼에 로딩 상태 표시를 추가하는 것을 권장합니다.

현재 isProcessing 상태일 때 버튼이 비활성화되지만, 사용자에게 처리 중임을 시각적으로 알려주는 로딩 인디케이터가 없습니다. 사용자 경험 개선을 위해 로딩 상태를 표시하는 것이 좋습니다.

다음과 같이 버튼에 로딩 상태를 추가할 수 있습니다:

 CommonButton(
     StringResources.Order.detailOrderCancel,
     isEnabled: isCancelEnabled,
+    isLoading: viewModel.uiState.isProcessing,
     backgroundColor: Color(.failRed),
     textColor: .white
 ) {
     showCancelDialog = true
 }

참고: CommonButtonisLoading 파라미터를 지원하지 않는다면, 버튼 위에 ProgressView를 오버레이하거나 버튼 컴포넌트를 확장해야 합니다.

SampoomManagement/Features/Cart/UI/CartListViewModel.swift (1)

54-56: 주문 결과 닫기 시 상태 리셋 범위 재검토.

현재 성공 플래그와 processedOrder만 초기화합니다. 이 이벤트가 성공/실패 공용 닫기라면 processError도 함께 비우는 편이 안전합니다.

가능한 변경 예:

- uiState = uiState.copy(isOrderSuccess: false, processedOrder: .some(nil))
+ uiState = uiState.copy(isOrderSuccess: false,
+                        processedOrder: .some(nil),
+                        processError: .some(nil))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e986baf and b9ef8c3.

📒 Files selected for processing (10)
  • SampoomManagement/App/ContentView.swift (4 hunks)
  • SampoomManagement/Core/Resources/StringResources.swift (1 hunks)
  • SampoomManagement/Core/Utilities/DateFormatterUtil.swift (1 hunks)
  • SampoomManagement/Features/Cart/UI/CartListUiState.swift (6 hunks)
  • SampoomManagement/Features/Cart/UI/CartListViewModel.swift (4 hunks)
  • SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderDetailContent.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderListView.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderListViewModel.swift (1 hunks)
  • SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift
  • SampoomManagement/Features/Order/UI/OrderListView.swift
  • SampoomManagement/Features/Cart/UI/CartListUiState.swift
  • SampoomManagement/Features/Order/UI/OrderDetailContent.swift
  • SampoomManagement/App/ContentView.swift
  • SampoomManagement/Core/Resources/StringResources.swift
🧰 Additional context used
🧬 Code graph analysis (3)
SampoomManagement/Features/Cart/UI/CartListViewModel.swift (2)
SampoomManagement/Features/Cart/UI/CartListUiState.swift (1)
  • copy (52-80)
SampoomManagement/Features/Order/Domain/UseCase/CreateOrderUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (2)
SampoomManagement/Features/Order/UI/OrderListUiState.swift (1)
  • copy (25-35)
SampoomManagement/Features/Order/Domain/UseCase/GetOrderUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (3)
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (4)
  • setOrderId (34-37)
  • onEvent (39-50)
  • cancelOrder (80-100)
  • clearSuccess (52-57)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1)
  • cancelOrder (61-68)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1)
  • cancelOrder (39-41)
🔇 Additional comments (8)
SampoomManagement/Features/Order/UI/OrderListViewModel.swift (4)

8-10: 필요한 임포트가 모두 적절히 포함되어 있습니다.

Foundation, SwiftUI, Combine은 ObservableObject 기반 ViewModel에 필요한 표준 임포트입니다.


12-20: 클래스 구조와 의존성 주입이 잘 설계되었습니다.

@MainActor 어노테이션과 의존성 주입 패턴이 적절하게 적용되어 있으며, use case 패턴을 따르고 있습니다.


22-27: 이벤트 처리 로직이 명확하고 깔끔합니다.

.loadOrderList.retryOrderList가 동일한 로드 로직을 호출하는 것이 합리적입니다.


29-51: MainActor 격리가 대부분 올바르게 적용되었습니다.

uiState 업데이트가 모두 await MainActor.run 블록 내에서 수행되어 이전 리뷰의 주요 문제가 해결되었습니다.

SampoomManagement/Core/Utilities/DateFormatterUtil.swift (1)

45-46: 파싱 실패 시 원본 문자열을 반환하는 것이 적절한지 검토하세요.

모든 파싱이 실패했을 때 원본 문자열을 반환하면 잘못된 형식의 데이터가 UI에 그대로 표시될 수 있습니다. 이는 데이터 품질 문제를 숨길 수 있으며, 사용자에게 혼란을 줄 수 있습니다.

다음과 같은 대안을 고려해보세요:

  • 옵셔널 반환 (String?)으로 변경하여 호출자가 실패를 처리하도록 함
  • 기본값(예: "날짜 없음", "N/A") 반환
  • 로깅 추가하여 파싱 실패를 추적

현재 구현이 의도된 동작인지 확인해주세요.

SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (2)

80-86: 이전 리뷰 이슈가 올바르게 수정되었습니다.

isCancelEnabled 계산 프로퍼티가 이전 리뷰에서 제안된 모든 조건을 올바르게 구현하고 있습니다:

  • 유효한 주문 ID 확인
  • 처리 중 상태 확인
  • 상세 데이터가 없을 때 초기 주문 데이터로 폴백
  • 완료/취소 상태 검증

로직이 안정적이고 엣지 케이스를 잘 처리하고 있습니다.


89-109: 헤더 구현이 깔끔합니다.

OrderCompleteHeader 구현이 명확하고 SwiftUI 베스트 프랙티스를 잘 따르고 있습니다. 레이아웃, 스타일링, 현지화가 모두 적절하게 처리되었습니다.

SampoomManagement/Features/Cart/UI/CartListViewModel.swift (1)

20-34: DI 전파 확인 완료 - 문제 없음.

createOrderUseCase 파라미터는 이미 AppDependencies.makeCartListViewModel() 메서드에서 모든 의존성과 함께 올바르게 전달되고 있습니다. CartListView 생성 지점(ContentView.swift:75)도 팩토리 메서드를 통해 호출되므로 추가 수정이 필요 없습니다.

Comment thread SampoomManagement/Core/Utilities/DateFormatterUtil.swift
Comment thread SampoomManagement/Core/Utilities/DateFormatterUtil.swift Outdated
Comment thread SampoomManagement/Features/Cart/UI/CartListViewModel.swift Outdated
Comment thread SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
SampoomManagement/Features/Cart/UI/CartListViewModel.swift (1)

87-114: 이전 리뷰의 이슈 중 2가지가 아직 해결되지 않았습니다.

대부분의 MainActor 경계 처리가 올바르게 수정되었으나, 다음 문제가 남아있습니다:

  1. Line 99: processedOrder[Order]?? 타입인데 orderList.items를 직접 전달하면 타입 불일치가 발생합니다. .some(orderList.items)로 감싸야 합니다.
  2. Line 113: print(uiState) 호출이 MainActor 격리된 uiState에 접근하므로 await MainActor.run 블록 내부로 이동해야 합니다.

다음 diff를 적용하여 수정하세요:

             await MainActor.run {
                 uiState = uiState.copy(
                     isOrderSuccess: true,
                     isProcessing: false,
-                    processedOrder: orderList.items
+                    processedOrder: .some(orderList.items)
                 )
             }
             await MainActor.run {
                 loadCartList() // 주문 후 장바구니 새로고침
             }
         } catch {
             await MainActor.run {
                 uiState = uiState.copy(
                     isProcessing: false,
                     processError: error.localizedDescription
                 )
             }
         }
-        print("CartListViewModel - processOrder: \(uiState)")
+        await MainActor.run {
+            print("CartListViewModel - processOrder: \(uiState)")
+        }
     }
🧹 Nitpick comments (4)
SampoomManagement/Core/Utilities/DateFormatterUtil.swift (3)

16-24: 포맷터 인스턴스를 정적 프로퍼티로 캐싱하여 성능을 개선하세요.

매 호출마다 새로운 ISO8601DateFormatterDateFormatter 인스턴스를 생성하는 것은 비용이 높습니다. 이 메서드가 자주 호출될 경우 성능 저하가 발생할 수 있습니다.

다음 diff를 적용하여 포맷터를 재사용하도록 개선하세요:

 struct DateFormatterUtil {
+    
+    private static let isoFormatter: ISO8601DateFormatter = {
+        let formatter = ISO8601DateFormatter()
+        formatter.formatOptions = [.withInternetDateTime]
+        return formatter
+    }()
+    
+    private static let outputFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "yyyy-MM-dd"
+        formatter.locale = Locale(identifier: "ko_KR")
+        formatter.timeZone = TimeZone.current
+        return formatter
+    }()
     
     /// ISO 8601 날짜 문자열을 로컬 날짜 형식으로 변환
     /// - Parameter dateString: ISO 8601 형식의 날짜 문자열
     /// - Returns: 변환된 날짜 문자열 (yyyy-MM-dd) 또는 원본 문자열 (실패 시)
     static func formatDate(_ dateString: String) -> String {
-        // ISO 8601 형식 파싱
-        let isoFormatter = ISO8601DateFormatter()
-        isoFormatter.formatOptions = [.withInternetDateTime]
-        
-        // 로컬 날짜 형식
-        let outputFormatter = Foundation.DateFormatter()
-        outputFormatter.dateFormat = "yyyy-MM-dd"
-        outputFormatter.locale = Locale(identifier: "ko_KR")
-        
         guard let date = isoFormatter.date(from: dateString) else {

22-24: 출력 포맷터의 타임존을 명시적으로 설정하세요.

outputFormatter에 타임존이 명시적으로 설정되어 있지 않아, 디바이스의 현재 타임존에 따라 다른 결과가 나올 수 있습니다. 일관된 동작을 위해 타임존을 명시하는 것이 좋습니다.

참고: 위의 포맷터 캐싱 제안에 formatter.timeZone = TimeZone.current 설정이 포함되어 있습니다.


28-43: 대체 포맷터 생성을 최적화하거나 제거를 고려하세요.

대체 포맷 배열의 형식들은 ISO 8601의 변형이며, 일부는 이미 ISO8601DateFormatter로 처리 가능합니다. 또한 fallback 루프에서 매번 새로운 DateFormatter 인스턴스를 생성하는 것은 비효율적입니다.

가능한 개선 방안:

  1. 대체 포맷터들도 정적 프로퍼티로 캐싱
  2. 타임존 정보가 없는 날짜 문자열을 받는 것이 요구사항인지 확인하고, 그렇지 않다면 fallback 로직 제거
  3. 타임존 없는 형식이 필요하다면, 단일 DateFormatter에 타임존을 설정하여 사용

예시:

private static let alternativeFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.locale = Locale(identifier: "ko_KR")
    formatter.timeZone = TimeZone(identifier: "UTC") // 또는 적절한 기본값
    return formatter
}()

// fallback 로직에서:
for format in alternativeFormatters {
    alternativeFormatter.dateFormat = format
    if let date = alternativeFormatter.date(from: dateString) {
        return outputFormatter.string(from: date)
    }
}
SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (1)

65-72: 취소 성공 시 불필요한 loadOrder 호출이 있습니다.

취소 성공 후 onDismiss()로 시트를 즉시 닫는데, 그 직전에 loadOrder를 호출하는 것은 불필요합니다. 시트가 닫히면 이 뷰모델의 상태는 더 이상 사용되지 않으며, 부모 뷰(CartListView)가 자체 목록을 갱신해야 합니다.

다음과 같이 수정하는 것을 고려하세요:

 .onChange(of: viewModel.uiState.isProcessingCancelSuccess) { _, newValue in
     if newValue {
-        // Toast 대신 Alert 사용하거나 다른 방법으로 처리
         viewModel.clearSuccess()
-        viewModel.onEvent(.loadOrder)
         onDismiss()
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9ef8c3 and 398af82.

📒 Files selected for processing (3)
  • SampoomManagement/Core/Utilities/DateFormatterUtil.swift (1 hunks)
  • SampoomManagement/Features/Cart/UI/CartListViewModel.swift (4 hunks)
  • SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (3)
SampoomManagement/Features/Order/UI/OrderDetailViewModel.swift (4)
  • setOrderId (34-37)
  • onEvent (39-50)
  • cancelOrder (80-100)
  • clearSuccess (52-57)
SampoomManagement/Features/Order/Data/Repository/OrderRepositoryImpl.swift (1)
  • cancelOrder (39-41)
SampoomManagement/Features/Order/Data/Remote/API/OrderAPI.swift (1)
  • cancelOrder (61-68)
SampoomManagement/Features/Cart/UI/CartListViewModel.swift (2)
SampoomManagement/Features/Cart/UI/CartListUiState.swift (1)
  • copy (52-80)
SampoomManagement/Features/Order/Domain/UseCase/CreateOrderUseCase.swift (1)
  • execute (17-19)
🔇 Additional comments (6)
SampoomManagement/Features/Cart/UI/CartListViewModel.swift (2)

20-20: 의존성 주입이 올바르게 구현되었습니다.

CreateOrderUseCase를 생성자를 통해 주입받는 표준 DI 패턴이 적절하게 적용되었습니다.

Also applies to: 26-27, 33-33


54-55: 이벤트 핸들러가 올바르게 구현되었습니다.

dismissOrderResult 이벤트 처리 시 .some(nil)을 사용하여 double optional 타입을 올바르게 처리하고 있습니다.

SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift (4)

11-17: 구조체 선언 및 프로퍼티 정의가 적절합니다.

필요한 프로퍼티들이 명확하게 정의되어 있으며, 이전 리뷰에서 지적된 onDismiss 콜백 미사용 문제가 70번 라인에서 해결되었습니다.


60-64: 초기 로딩 로직이 올바르게 설계되었습니다.

시트가 열릴 때 이미 order 파라미터로 데이터가 전달되므로, onAppear에서 별도로 loadOrder를 호출할 필요가 없습니다. orderId만 설정하는 현재 구조가 적절합니다.


81-87: 취소 버튼 활성화 조건이 안정적으로 구현되었습니다.

이전 리뷰에서 지적된 문제가 모두 해결되었습니다:

  • orderId 존재 여부 확인
  • 처리 중 상태 확인
  • orderDetail 또는 초기 order를 안전하게 사용
  • 완료/취소된 주문은 비활성화

90-110: 헤더 뷰 구현이 깔끔합니다.

주문 완료 상태를 명확하게 표시하는 심플한 구조입니다. 스타일링과 레이아웃이 적절합니다.

Comment thread SampoomManagement/Features/Order/UI/OrderResultBottomSheet.swift
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge 3명 이상의 리뷰어에게 승인되어 병합 준비가 완료된 PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants