[FEAT] 부품 조회 화면 구현#13
Conversation
Walkthrough부품 흐름을 카테고리→그룹→부품 계층으로 재구성하고 DTO/모델/레포/유스케이스/뷰모델/뷰를 추가/변경했습니다. RootView 인증 분기 로직이 반전되었고 User의 토큰 및 일부 컨포던스가 제거되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ContentView
participant NavigationStack
participant PartView
participant PartViewModel
participant Repo
participant PartListView
participant PartListViewModel
User->>ContentView: 부품 탭 선택
ContentView->>NavigationStack: PartView 표시 (navigationPath)
NavigationStack->>PartView: render
PartView->>PartViewModel: onEvent(.loadCategories)
PartViewModel->>Repo: getCategoryList()
Repo-->>PartViewModel: CategoryList
PartViewModel-->>PartView: categoryList 업데이트
User->>PartView: 카테고리 선택
PartView->>PartViewModel: onEvent(.categorySelected(category))
PartViewModel->>Repo: getGroupList(categoryId)
Repo-->>PartViewModel: PartsGroupList
PartViewModel-->>PartView: groupList 업데이트
User->>PartView: 그룹 선택
PartView->>NavigationStack: onNavigatePartList(groupId)
NavigationStack->>PartListView: push (groupId)
PartListView->>PartListViewModel: init(groupId)
PartListViewModel->>Repo: getPartList(groupId)
Repo-->>PartListViewModel: PartList
PartListViewModel-->>PartListView: uiState 업데이트
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (25)
SampoomManagement/Core/UI/Components/EmptyView.swift (3)
11-23: 제목 문자열 i18n 대응 강화
title을String으로 받으면Text(title)가 지역화 경로를 타지 않습니다. 고정 문구 성격이므로LocalizedStringKey로 전환을 권장합니다. 기존 호출 대부분은 리터럴이라 영향 최소입니다.적용 예시(diff):
- let title: String + let title: LocalizedStringKey - init( - icon: String = "tray", - title: String = "데이터가 없습니다", - message: String? = nil - ) { + init( + icon: String = "tray", + title: LocalizedStringKey = "데이터가 없습니다", + message: String? = nil + ) {참고:
message는 동적 문구일 수 있어String?유지 제안.
25-47: 접근성(a11y) 개선: 장식 아이콘 숨김, 헤더 지정, 자식 결합
- 아이콘은 장식 용도이므로 스크린리더에서 숨김.
- 제목에 헤더 트레잇.
- 텍스트를 하나의 요소로 묶어 읽기 개선.
적용 예시(diff):
var body: some View { - VStack(spacing: 16) { + VStack(spacing: 16) { Spacer() Image(systemName: icon) .font(.system(size: 48)) .foregroundColor(.secondary) + .accessibilityHidden(true) Text(title) .font(.headline) .foregroundColor(.secondary) + .accessibilityAddTraits(.isHeader) if let message = message { Text(message) .font(.caption) .foregroundColor(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 32) } Spacer() - } + } + .accessibilityElement(children: .combine) }
48-48: 개발 편의성: SwiftUI Preview 복원 권장화면/다크모드/동적 폰트 점검이 빨라집니다. 타입명 변경 후 아래 미리보기 추가를 권장합니다.
예시:
#if DEBUG struct EmptyStateView_Previews: PreviewProvider { static var previews: some View { Group { EmptyStateView() EmptyStateView(icon: "wrench.and.screwdriver", title: "부품이 없습니다", message: "필터를 변경하거나 다시 시도하세요.") } .previewLayout(.sizeThatFits) .padding() } } #endifSampoomManagement/Features/Part/Domain/UseCase/GetCategoryUseCase.swift (1)
11-11: 코딩 스타일 개선 제안Swift 컨벤션에 따르면 콜론 앞에는 공백이 없고 뒤에만 공백이 있어야 합니다.
- private let repository : PartRepository + private let repository: PartRepositorySampoomManagement/Features/Part/UI/PartListViewModel.swift (1)
14-14: 외부 변이 방지: UI 상태 setter 은닉뷰 외부에서 임의 변경을 막기 위해
@Published private(set) var uiState로 노출을 축소하는 것을 권장합니다. 바인딩은 그대로 동작하고, 변경은 ViewModel 내부로 한정됩니다.- @Published var uiState = PartListUiState() + @Published private(set) var uiState = PartListUiState()SampoomManagement/Features/Part/Domain/Models/Part.swift (1)
10-15:Identifiable채택 고려여러 곳에서
id를 기준으로 렌더링/식별합니다.Identifiable채택 시ForEach등에서 보일러플레이트 제거와 오타 방지가 가능합니다.-struct Part: Equatable { +struct Part: Identifiable, Equatable { let id: Int let code: String let name: String let quantity: Int }SampoomManagement/Core/DI/AppDependencies.swift (1)
12-12: DI 컨테이너는final권장상속이 필요 없는 DI 컨테이너는
final로 명시해 동적 디스패치 비용을 줄이고 의도를 명확히 하세요.-class AppDependencies { +final class AppDependencies {SampoomManagement/Features/Part/Domain/Repository/PartRepository.swift (1)
10-14: 도메인 식별자 타입/문서화 제안
Int기반 ID는 혼동 여지가 큽니다.typealias CategoryID = Int,typealias GroupID = Int등 도메인 타입을 도입하거나 메서드 주석으로 계약을 명확히 해 주세요. API 오용을 줄입니다.SampoomManagement/Features/Part/UI/PartListUiState.swift (1)
10-36:Equatable채택으로 비교·테스트 용이성 향상UI 상태 스냅샷 테스트 및 변경 감지(디버깅)에 유리합니다. 저장 프로퍼티가 모두
Equatable이므로 자동 합성 가능합니다.-struct PartListUiState { +struct PartListUiState: Equatable {SampoomManagement/Features/Part/Data/Repository/PartRepositoryImpl.swift (1)
10-28:final로 클래스 확정상속 계획이 없다면
final로 지정하여 성능과 의도를 개선하세요.-class PartRepositoryImpl: PartRepository { +final class PartRepositoryImpl: PartRepository {SampoomManagement/Features/Part/UI/PartListView.swift (2)
19-56: 당김 새로고침 추가로 재시도 UX 개선네트워크 목록 UI에 기본
refreshable을 추가해 자연스러운 재로딩을 지원하세요.} else { // 부품 리스트 - ScrollView { + ScrollView { LazyVStack(spacing: 8) { ForEach(viewModel.uiState.partList, id: \.id) { part in PartListItemCard(part: part) } } .padding(16) - } + } + .refreshable { + viewModel.onEvent(.loadPartList) + } }
61-91: 접근성 개선: 라벨/값 및 터치 영역 명시VoiceOver 사용자와 터치 정확도를 위해 카드에 접근성 라벨/값과 전체 터치 영역을 지정하세요.
struct PartListItemCard: View { let part: Part var body: some View { - HStack { + HStack { VStack(alignment: .leading, spacing: 4) { Text(part.name) .font(.gmarketTitle2) .foregroundColor(.primary) Text(part.code) .font(.gmarketCaption) .foregroundColor(.secondary) } Spacer() - Text("\(part.quantity)") + Text("\(part.quantity)") .font(.gmarketTitle2) .foregroundColor(.primary) Image(systemName: "chevron.right") .foregroundColor(.secondary) } .padding(16) .background( RoundedRectangle(cornerRadius: 8) .fill(Color("BackgroundCard")) ) + .contentShape(RoundedRectangle(cornerRadius: 8)) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(part.name), 코드 \(part.code)") + .accessibilityValue("수량 \(part.quantity)") } }SampoomManagement/App/ContentView.swift (3)
151-158: DI 일관성: ViewModel 생성은 팩토리 사용직접 생성 대신
AppDependencies.makePartListViewModel(groupId:)를 사용해 의존성 구성을 한 곳에서 관리하세요.- .navigationDestination(for: Int.self) { groupId in - PartListView( - viewModel: PartListViewModel( - getPartUseCase: dependencies.getPartUseCase, - groupId: groupId - ) - ) - } + .navigationDestination(for: Int.self) { groupId in + PartListView( + viewModel: dependencies.makePartListViewModel(groupId: groupId) + ) + }
172-173:.accentColor대신.tint사용 권장최신 SwiftUI에서는
.tint(_:)가 권장됩니다.- .accentColor(.blue) + .tint(.blue)
159-161: 중복 주입 가능성 점검
PartView에viewModel: partViewModel를 전달하면서 동일 뷰 트리에.environmentObject(partViewModel)도 주입합니다. 이중 주입이 API 의도와 맞는지 확인 바랍니다(하나는 제거해도 충분할 수 있음).SampoomManagement/Features/Part/Data/Remote/API/PartAPI.swift (2)
17-33: 카테고리 목록 API 연동 전반적으로 적절합니다.매핑·에러처리·continuation 사용 모두 무난합니다. 중복 제거는 아래 코멘트 참고해 주세요.
17-33: 중복된 요청/매핑 보일러플레이트를 제네릭 헬퍼로 축소하세요.세 메서드가 동일 패턴(요청→[DTO]→[Model]→ListWrapper)입니다. 가독성과 유지보수를 위해 공통화 권장합니다.
예시(동일 파일 내부 private 헬퍼):
private func requestList<DTO: Codable, Model>( endpoint: String, map: @escaping (DTO) -> Model ) async throws -> [Model] { try await withCheckedThrowingContinuation { continuation in networkManager.request(endpoint: endpoint, responseType: [DTO].self) { result in switch result { case .success(let response): continuation.resume(returning: response.data.map(map)) case .failure(let error): continuation.resume(throwing: error) } } } }사용:
let items = try await requestList(endpoint: "agency/category", map: { (dto: CategoryDTO) in dto.toModel() }) return CategoryList(items: items)Also applies to: 35-51, 53-69
SampoomManagement/Features/Part/UI/PartUIState.swift (1)
10-41: State 자체는 깔끔합니다. Equatable 채택을 고려해 주세요.Diff 기반 렌더 최적화를 위해
PartUIState: Equatable채택을 권장합니다(모든 필드가 Equatable이면 자동 합성).SampoomManagement/Features/Part/UI/PartViewModel.swift (2)
15-26: DI 및 초기 로딩 타이밍 양호합니다.초기화 시 카테고리 로딩 트리거는 UX상 적절합니다. 필요 시
onAppear로의 이동은 선택사항입니다.
58-59: print 디버그 로그는 운영 빌드에서 제거/가드하세요.로거로 대체하거나
#if DEBUG가드를 권장합니다.예시:
#if DEBUG print("PartViewModel - loadCategory: \(uiState)") #endifAlso applies to: 86-86
SampoomManagement/Features/Part/UI/PartView.swift (5)
49-49: 검색 프롬프트 현지화 및 실제 필터링 연동 권장
"search"하드코딩은 i18n 미흡입니다. LocalizedStringKey로 교체하고searchQuery를 그룹/카테고리 필터링에 반영하세요.예시:
.searchable(text: $searchQuery, prompt: Text("search_prompt"))
72-75: 커스텀 EmptyView 명칭이 SwiftUI.EmptyView와 혼동될 수 있습니다.이름 충돌/가독성 문제를 피하려면
PlaceholderView등으로 변경을 고려하세요.Also applies to: 133-136
94-151: 그룹 섹션도 상태 분기 명확합니다.UI 흐름 일관적입니다. 검색어 필터만 추가되면 UX가 더 좋아질 것 같습니다.
156-197: CategoryItem 접근성/가독성 개선 제안
- 버튼에 접근성 라벨/선택 상태를 명시하세요.
- 다크모드 대비를 위해 선택·비선택 색상의 대비를 점검하세요.
예시:
.accessibilityLabel(Text(category.name)) .accessibilityAddTraits(isSelected ? .isSelected : [])
199-223: 배경 색상 사용 일관화
Color(.backgroundCard)와.backgroundCard확장 방식이 혼재하면 테마 적용이 어려워집니다. 하나로 통일하세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
SampoomManagement/Resources/Assets.xcassets/body.imageset/body.svgis excluded by!**/*.svgSampoomManagement/Resources/Assets.xcassets/chassis.imageset/chassis.svgis excluded by!**/*.svgSampoomManagement/Resources/Assets.xcassets/electric.imageset/electric.svgis excluded by!**/*.svgSampoomManagement/Resources/Assets.xcassets/engine.imageset/engine.svgis excluded by!**/*.svgSampoomManagement/Resources/Assets.xcassets/transmission.imageset/transmission.svgis excluded by!**/*.svgSampoomManagement/Resources/Assets.xcassets/trim.imageset/trim.svgis excluded by!**/*.svg
📒 Files selected for processing (38)
SampoomManagement/App/ContentView.swift(6 hunks)SampoomManagement/App/RootView.swift(1 hunks)SampoomManagement/Core/DI/AppDependencies.swift(3 hunks)SampoomManagement/Core/UI/Components/EmptyView.swift(1 hunks)SampoomManagement/Core/UI/Components/ErrorView.swift(1 hunks)SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift(0 hunks)SampoomManagement/Features/Auth/Domain/Models/User.swift(1 hunks)SampoomManagement/Features/Part/Data/Mappers/PartMappers.swift(1 hunks)SampoomManagement/Features/Part/Data/Remote/API/PartAPI.swift(1 hunks)SampoomManagement/Features/Part/Data/Remote/DTO/CategoryDTO.swift(1 hunks)SampoomManagement/Features/Part/Data/Remote/DTO/GroupDTO.swift(1 hunks)SampoomManagement/Features/Part/Data/Remote/DTO/PartDTO.swift(1 hunks)SampoomManagement/Features/Part/Data/Repository/PartRepositoryImpl.swift(1 hunks)SampoomManagement/Features/Part/Domain/Models/Category.swift(1 hunks)SampoomManagement/Features/Part/Domain/Models/CategoryList.swift(1 hunks)SampoomManagement/Features/Part/Domain/Models/Part.swift(1 hunks)SampoomManagement/Features/Part/Domain/Models/PartList.swift(1 hunks)SampoomManagement/Features/Part/Domain/Models/PartsGroup.swift(1 hunks)SampoomManagement/Features/Part/Domain/Models/PartsGroupList.swift(1 hunks)SampoomManagement/Features/Part/Domain/Repository/PartRepository.swift(1 hunks)SampoomManagement/Features/Part/Domain/UseCase/GetCategoryUseCase.swift(1 hunks)SampoomManagement/Features/Part/Domain/UseCase/GetGroupUseCase.swift(1 hunks)SampoomManagement/Features/Part/Domain/UseCase/GetPartUseCase.swift(1 hunks)SampoomManagement/Features/Part/UI/PartItemView.swift(0 hunks)SampoomManagement/Features/Part/UI/PartListUiEvent.swift(1 hunks)SampoomManagement/Features/Part/UI/PartListUiState.swift(1 hunks)SampoomManagement/Features/Part/UI/PartListView.swift(1 hunks)SampoomManagement/Features/Part/UI/PartListViewModel.swift(1 hunks)SampoomManagement/Features/Part/UI/PartUIState.swift(1 hunks)SampoomManagement/Features/Part/UI/PartUiEvent.swift(1 hunks)SampoomManagement/Features/Part/UI/PartView.swift(1 hunks)SampoomManagement/Features/Part/UI/PartViewModel.swift(1 hunks)SampoomManagement/Resources/Assets.xcassets/body.imageset/Contents.json(1 hunks)SampoomManagement/Resources/Assets.xcassets/chassis.imageset/Contents.json(1 hunks)SampoomManagement/Resources/Assets.xcassets/electric.imageset/Contents.json(1 hunks)SampoomManagement/Resources/Assets.xcassets/engine.imageset/Contents.json(1 hunks)SampoomManagement/Resources/Assets.xcassets/transmission.imageset/Contents.json(1 hunks)SampoomManagement/Resources/Assets.xcassets/trim.imageset/Contents.json(1 hunks)
💤 Files with no reviewable changes (2)
- SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift
- SampoomManagement/Features/Part/UI/PartItemView.swift
🧰 Additional context used
🧬 Code graph analysis (16)
SampoomManagement/Features/Part/Domain/UseCase/GetPartUseCase.swift (3)
SampoomManagement/Features/Part/Domain/UseCase/GetGroupUseCase.swift (1)
execute(17-19)SampoomManagement/Features/Part/Data/Remote/API/PartAPI.swift (1)
getPartList(53-69)SampoomManagement/Features/Part/Data/Repository/PartRepositoryImpl.swift (1)
getPartList(25-27)
SampoomManagement/Features/Part/UI/PartListUiState.swift (1)
SampoomManagement/Features/Part/UI/PartUIState.swift (1)
copy(43-61)
SampoomManagement/Features/Part/Domain/UseCase/GetCategoryUseCase.swift (4)
SampoomManagement/Features/Part/Domain/UseCase/GetGroupUseCase.swift (1)
execute(17-19)SampoomManagement/Features/Part/Domain/UseCase/GetPartUseCase.swift (1)
execute(17-19)SampoomManagement/Features/Part/Data/Remote/API/PartAPI.swift (1)
getCategoryList(17-33)SampoomManagement/Features/Part/Data/Repository/PartRepositoryImpl.swift (1)
getCategoryList(17-19)
SampoomManagement/Features/Part/Domain/Models/PartsGroupList.swift (2)
SampoomManagement/Features/Part/Domain/Models/CategoryList.swift (1)
empty(19-21)SampoomManagement/Features/Part/Domain/Models/PartList.swift (1)
empty(19-21)
SampoomManagement/Features/Part/Domain/Models/CategoryList.swift (2)
SampoomManagement/Features/Part/Domain/Models/PartList.swift (1)
empty(19-21)SampoomManagement/Features/Part/Domain/Models/PartsGroupList.swift (1)
empty(19-21)
SampoomManagement/Features/Part/UI/PartListViewModel.swift (2)
SampoomManagement/Features/Part/UI/PartListUiState.swift (1)
copy(25-35)SampoomManagement/Features/Part/Domain/UseCase/GetPartUseCase.swift (1)
execute(17-19)
SampoomManagement/Features/Part/Data/Remote/API/PartAPI.swift (3)
SampoomManagement/Features/Part/Data/Repository/PartRepositoryImpl.swift (3)
getCategoryList(17-19)getGroupList(21-23)getPartList(25-27)SampoomManagement/Core/Network/NetworkManager.swift (1)
request(18-50)SampoomManagement/Features/Part/Data/Mappers/PartMappers.swift (3)
toModel(11-17)toModel(21-28)toModel(32-39)
SampoomManagement/Features/Part/Domain/UseCase/GetGroupUseCase.swift (4)
SampoomManagement/Features/Part/Domain/UseCase/GetCategoryUseCase.swift (1)
execute(17-19)SampoomManagement/Features/Part/Domain/UseCase/GetPartUseCase.swift (1)
execute(17-19)SampoomManagement/Features/Part/Data/Remote/API/PartAPI.swift (1)
getGroupList(35-51)SampoomManagement/Features/Part/Data/Repository/PartRepositoryImpl.swift (1)
getGroupList(21-23)
SampoomManagement/Features/Part/UI/PartListView.swift (1)
SampoomManagement/Features/Part/UI/PartListViewModel.swift (1)
onEvent(29-36)
SampoomManagement/Features/Part/UI/PartListUiEvent.swift (1)
SampoomManagement/Features/Part/UI/PartListViewModel.swift (1)
loadPartList(38-57)
SampoomManagement/Features/Part/Data/Repository/PartRepositoryImpl.swift (1)
SampoomManagement/Features/Part/Data/Remote/API/PartAPI.swift (3)
getCategoryList(17-33)getGroupList(35-51)getPartList(53-69)
SampoomManagement/Features/Part/Domain/Repository/PartRepository.swift (2)
SampoomManagement/Features/Part/Data/Remote/API/PartAPI.swift (3)
getCategoryList(17-33)getGroupList(35-51)getPartList(53-69)SampoomManagement/Features/Part/Data/Repository/PartRepositoryImpl.swift (3)
getCategoryList(17-19)getGroupList(21-23)getPartList(25-27)
SampoomManagement/Features/Part/UI/PartUIState.swift (2)
SampoomManagement/Features/Part/UI/PartListUiState.swift (1)
copy(25-35)SampoomManagement/Core/UI/State/UIState.swift (1)
copy(31-41)
SampoomManagement/Features/Part/UI/PartViewModel.swift (3)
SampoomManagement/Features/Part/UI/PartUIState.swift (1)
copy(43-61)SampoomManagement/Features/Part/Domain/UseCase/GetCategoryUseCase.swift (1)
execute(17-19)SampoomManagement/Features/Part/Domain/UseCase/GetGroupUseCase.swift (1)
execute(17-19)
SampoomManagement/Features/Part/UI/PartView.swift (1)
SampoomManagement/Features/Part/UI/PartViewModel.swift (1)
onEvent(28-39)
SampoomManagement/Features/Part/Data/Mappers/PartMappers.swift (1)
SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (1)
toModel(11-18)
🔇 Additional comments (29)
SampoomManagement/Resources/Assets.xcassets/transmission.imageset/Contents.json (1)
1-12: 구조 및 형식이 올바릅니다.전송 장치 아이콘에 대한 자산 카탈로그 메타데이터가 표준 Xcode 형식을 따르고 있습니다.
SampoomManagement/Resources/Assets.xcassets/engine.imageset/Contents.json (1)
1-12: 구조 및 형식이 올바릅니다.엔진 아이콘에 대한 자산 카탈로그 메타데이터가 표준 Xcode 형식을 따르고 있습니다.
SampoomManagement/Resources/Assets.xcassets/body.imageset/Contents.json (1)
1-12: 구조 및 형식이 올바릅니다.바디 아이콘에 대한 자산 카탈로그 메타데이터가 표준 Xcode 형식을 따르고 있습니다.
SampoomManagement/Resources/Assets.xcassets/trim.imageset/Contents.json (1)
1-12: 구조 및 형식이 올바릅니다.트림 아이콘에 대한 자산 카탈로그 메타데이터가 표준 Xcode 형식을 따르고 있습니다.
SampoomManagement/Resources/Assets.xcassets/electric.imageset/Contents.json (1)
1-12: 구조 및 형식이 올바릅니다.전기 아이콘에 대한 자산 카탈로그 메타데이터가 표준 Xcode 형식을 따르고 있습니다.
SampoomManagement/Resources/Assets.xcassets/chassis.imageset/Contents.json (1)
1-12: 검증 완료: 모든 자산 파일이 올바르게 등록되었습니다.
chassis.imageset디렉토리에서Contents.json(157 바이트)과 대응하는chassis.svg파일 (1693 바이트)이 모두 존재하며, JSON 참조가 올바르게 설정되어 있습니다. 부품 조회 화면의 6가지 카테고리(바디, 섀시, 전기, 엔진, 전송, 트림) 아이콘이 모두 자산 카탈로그에 정상 등록되었습니다.SampoomManagement/Core/UI/Components/ErrorView.swift (2)
32-37: 버튼 텍스트 크기를 검토하세요.버튼 텍스트에
.gmarketCaption폰트를 사용하고 있는데, caption 폰트는 일반적으로 작은 크기입니다. 주요 액션 버튼의 경우 가독성과 접근성을 위해 더 큰 폰트(예:.gmarketBody또는.gmarketSubheadline)를 사용하는 것이 좋습니다.실제 기기나 시뮬레이터에서 버튼 텍스트 크기가 적절한지 확인하고, 필요시 더 큰 폰트로 변경하는 것을 고려하세요.
23-23: 커스텀 폰트가 올바르게 정의되어 있습니다.
.gmarketHeadline은Font+Extension.swift에서 제대로 정의되었습니다.GmarketSansWeight.medium케이스가 존재하며, "GmarketSansMedium" 폰트 이름으로 매핑되어 있습니다. 전체 구현 체인이 완전하고 정상 작동하므로 추가 조치가 필요하지 않습니다.SampoomManagement/Core/UI/Components/EmptyView.swift (1)
10-10: 네이밍 충돌 위험 재평가 — 실제 위험은 없으나 코드 명확성 개선 권장검증 결과, 이 구조체는
EmptyView(icon:, title:, message:)파라미터를 필수로 요구하는 반면, SwiftUI.EmptyView는 파라미터가 없습니다. 따라서 현재 사용 패턴(모두 명명된 파라미터 전달)에서 Swift 컴파일러는 시그니처 매칭으로 커스텀 버전만 올바르게 해석합니다. 파라미터 없는EmptyView()호출은 코드베이스에 없고, 있더라도 SwiftUI.EmptyView로 의도대로 해석됩니다.그러나 코드 명확성을 위해 여전히 이름 변경을 권장합니다(예:
EmptyStateView,PlaceholderView). 이는 혼동을 방지하고 의도를 명확히 합니다.-struct EmptyView: View { +struct EmptyStateView: View {변경 후 다음 3개 사용처 업데이트:
SampoomManagement/Features/Part/UI/PartView.swift(72, 133줄)SampoomManagement/Features/Part/UI/PartListView.swift(38줄)Likely an incorrect or invalid review comment.
SampoomManagement/Features/Auth/Domain/Models/User.swift (1)
10-15: 도메인 모델에서 인증 토큰 제거는 적절한 설계입니다.
User도메인 모델에서Codable및 토큰 속성(accessToken,refreshToken)을 제거한 것은 관심사의 분리(separation of concerns) 측면에서 바람직합니다. 인증 토큰은 인프라/데이터 계층에서 관리되어야 하며, 도메인 모델은 비즈니스 로직에 집중해야 합니다.SampoomManagement/Features/Part/Data/Mappers/PartMappers.swift (1)
10-40: 매퍼 구현이 정확하고 일관성 있습니다.새로 추가된
CategoryDTO와GroupDTO매퍼, 그리고 업데이트된PartDTO매퍼가 모두 DTO의 필드를 도메인 모델로 올바르게 변환하고 있습니다. 특히PartDTO의partId,quantity필드명 변경이 정확히 반영되었습니다.SampoomManagement/Features/Part/Data/Remote/DTO/PartDTO.swift (1)
10-15: DTO 필드 변경이 적절합니다.
id→partId,count→quantity로의 변경은 명확성을 높이며, 새로운code필드 추가도 계층적 데이터 구조(Category → Group → Part)를 지원합니다. 매퍼와 일관성 있게 정의되었습니다.SampoomManagement/Features/Part/Domain/Models/CategoryList.swift (1)
10-22: 일관된 리스트 래퍼 패턴입니다.
CategoryList가 기존PartList,PartsGroupList와 동일한 패턴을 따르고 있어 코드베이스의 일관성이 유지됩니다.totalCount,isEmpty계산 속성과empty()팩토리 메서드 구현이 적절합니다.SampoomManagement/Features/Part/Domain/Models/PartsGroupList.swift (1)
10-22: 구현이 다른 리스트 타입과 일관성 있습니다.
PartsGroupList가CategoryList및PartList와 동일한 패턴을 따르고 있어 예측 가능하고 유지보수하기 쉽습니다.SampoomManagement/Features/Part/UI/PartListUiEvent.swift (1)
10-13: UI 이벤트 정의가 명확합니다.
loadPartList와retryPartList두 케이스로 부품 목록 로딩 시나리오를 적절히 표현하고 있습니다. 이벤트 기반 UI 아키텍처에 적합한 구조입니다.SampoomManagement/Features/Part/Data/Remote/DTO/GroupDTO.swift (1)
10-15: 계층적 데이터 구조를 지원하는 DTO입니다.
GroupDTO가categoryId를 통해 Category와의 관계를 명확히 표현하고 있으며,code와name필드가 적절히 정의되어 있습니다. API 응답 매핑에 적합한 구조입니다.SampoomManagement/Features/Part/Domain/Models/PartList.swift (1)
10-10: 도메인 모델에서 Codable 제거는 좋은 아키텍처 결정입니다.
PartList에서Codable프로토콜 준수를 제거한 것은 도메인 모델을 직렬화 관심사로부터 분리하는 올바른 설계입니다. 이는User모델의 변경과도 일관성이 있으며, 계층 간 책임 분리를 강화합니다.SampoomManagement/Features/Part/Domain/Models/Category.swift (1)
10-14: LGTM!카테고리 도메인 모델이 간결하고 명확하게 정의되어 있습니다.
Equatable프로토콜 준수로 비교 연산이 가능하며, 불변 속성으로 선언되어 데이터 무결성이 보장됩니다.SampoomManagement/Features/Part/Domain/Models/PartsGroup.swift (1)
10-15: LGTM!그룹 도메인 모델이 적절하게 정의되어 있습니다.
categoryId를 통해 카테고리와의 관계가 명확하게 표현되어 있습니다.SampoomManagement/Features/Part/UI/PartUiEvent.swift (1)
10-15: LGTM!UI 이벤트가 명확하게 정의되어 있습니다. 카테고리 로딩, 선택, 재시도 등의 이벤트가 적절하게 구조화되어 있으며, 계층적 데이터 흐름(카테고리 → 그룹)을 잘 지원합니다.
SampoomManagement/Features/Part/Domain/UseCase/GetCategoryUseCase.swift (1)
10-20: LGTM!클린 아키텍처 패턴을 잘 따르고 있습니다. 의존성 주입을 통한 repository 사용과 비동기 처리가 적절하게 구현되어 있습니다.
SampoomManagement/Features/Part/Data/Remote/DTO/CategoryDTO.swift (1)
10-14: LGTM!데이터 전송 객체가 적절하게 정의되어 있습니다.
Codable프로토콜 준수로 JSON 직렬화/역직렬화가 자동으로 처리되며, 도메인 모델Category와 구조가 일치하여 매핑이 명확합니다.SampoomManagement/Features/Part/Domain/UseCase/GetGroupUseCase.swift (1)
10-20: LGTM!그룹 조회 유스케이스가 잘 구현되어 있습니다.
categoryId매개변수를 통해 계층적 데이터 흐름(카테고리 → 그룹)을 지원하며, 클린 아키텍처 패턴을 일관되게 따르고 있습니다.SampoomManagement/Features/Part/Domain/UseCase/GetPartUseCase.swift (1)
17-18: LGTM!메서드 시그니처 변경이 적절합니다.
groupId매개변수 추가로 계층적 데이터 조회(카테고리 → 그룹 → 부품)를 지원하며,GetGroupUseCase와 일관된 패턴을 유지하고 있습니다.SampoomManagement/Features/Part/Data/Remote/API/PartAPI.swift (2)
35-51: 그룹 목록 API 연동도 전반적으로 무난합니다.성공/실패 경로에서 정확히 resume 하고 있으며 DTO→모델 매핑도 일관적입니다.
53-69: 엔드포인트의 하드코딩된 agencyId 제거 필수 - 아키텍처 검증 필요하드코딩된
"agency/1"은 확인되었으며, 다계정 시나리오에서 오작동 위험이 실제로 존재합니다. 다만 제안된 수정을 구현하려면 다음을 확인하고 진행해야 합니다:
- agencyId의 출처 결정: 현재 코드베이스에서 agencyId가 어디서도 관리되지 않고 있습니다. 인증 세션, 앱 상태, 또는 의존성 주입을 통해 어디서 agencyId를 획득할지 아키텍처 검토 필요
- 호출 체인 변경:
GetPartUseCase→PartRepositoryImpl→PartAPI.getPartList()의 3개 계층 전수정 필요- 모든 호출 지점 업데이트: 리포지토리 메서드 서명 변경에 따른 전체 호출처 반영 필수
- func getPartList(groupId: Int) async throws -> PartList { + func getPartList(agencyId: Int, groupId: Int) async throws -> PartList { return try await withCheckedThrowingContinuation { continuation in networkManager.request( - endpoint: "agency/1/group/\(groupId)", + endpoint: "agency/\(agencyId)/group/\(groupId)", responseType: [PartDTO].selfSampoomManagement/Features/Part/UI/PartViewModel.swift (2)
62-68: 카테고리 선택 플로우는 적절합니다.선택 상태 업데이트 후 그룹 로드를 트리거하는 순서가 명확합니다. 위 loadGroup 수정과 함께 문제없습니다.
loadGroup 수정 후에도 컴파일·동작 확인 부탁드립니다.
28-39: 이벤트 라우팅 깔끔합니다.UI 이벤트 분기 구조 명확합니다. 그룹 재시도는
loadGroup()로 위임되어 일관적입니다.SampoomManagement/Features/Part/UI/PartView.swift (1)
54-91: 카테고리 섹션 상태 처리 구조 좋습니다.로딩/에러/빈/정상 상태가 명확하게 분기되어 있습니다. 에러 재시도 이벤트도 일관적입니다.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
SampoomManagement/Core/UI/Components/ErrorView.swift (1)
11-11:error파라미터가 사용되지 않습니다.이전 리뷰에서 지적된 문제가 여전히 남아있습니다.
error파라미터가 프로퍼티로 저장되지만 뷰 본문 어디에서도 사용되지 않습니다. API가 오해를 불러일으킬 수 있으므로 다음 중 하나를 선택하세요:
- 오류 상세 정보가 필요 없다면
error파라미터를 제거- 오류 메시지를 다시 표시하도록 UI 복원
🧹 Nitpick comments (2)
SampoomManagement/Features/Part/UI/PartListViewModel.swift (1)
32-33: 코드 포맷팅을 개선하세요.콜론 뒤에 공백이 누락되어 가독성이 떨어집니다.
다음 diff를 적용하세요:
- case .loadPartList:loadPartList() - case .retryPartList:loadPartList() + case .loadPartList: loadPartList() + case .retryPartList: loadPartList()SampoomManagement/Features/Part/UI/PartViewModel.swift (1)
55-55: 에러 설정 시 명시적 래핑을 권장합니다.에러를 지울 때는
.some(nil)을 사용하고 있지만, 에러를 설정할 때는 암시적 타입 승격에 의존하고 있습니다. 일관성과 명확성을 위해 에러 설정 시에도 명시적으로 래핑하는 것을 권장합니다.다음 diff를 적용하여 일관성을 개선하세요:
} catch { uiState = uiState.copy( categoryLoading: false, - categoryError: error.localizedDescription + categoryError: .some(error.localizedDescription) ) }그리고 line 82에도 동일하게 적용:
} catch { uiState = uiState.copy( groupLoading: false, - groupError: error.localizedDescription + groupError: .some(error.localizedDescription) ) }Also applies to: 82-82
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
SampoomManagement/App/ContentView.swift(6 hunks)SampoomManagement/Core/UI/Components/ErrorView.swift(1 hunks)SampoomManagement/Features/Part/Domain/Models/PartsGroup.swift(1 hunks)SampoomManagement/Features/Part/Domain/Models/PartsGroupList.swift(1 hunks)SampoomManagement/Features/Part/UI/PartListViewModel.swift(1 hunks)SampoomManagement/Features/Part/UI/PartUIState.swift(1 hunks)SampoomManagement/Features/Part/UI/PartView.swift(1 hunks)SampoomManagement/Features/Part/UI/PartViewModel.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- SampoomManagement/Features/Part/Domain/Models/PartsGroup.swift
🧰 Additional context used
🧬 Code graph analysis (5)
SampoomManagement/Features/Part/UI/PartListViewModel.swift (2)
SampoomManagement/Features/Part/UI/PartListUiState.swift (1)
copy(25-35)SampoomManagement/Features/Part/Domain/UseCase/GetPartUseCase.swift (1)
execute(17-19)
SampoomManagement/Features/Part/UI/PartUIState.swift (2)
SampoomManagement/Features/Part/UI/PartListUiState.swift (1)
copy(25-35)SampoomManagement/Core/UI/State/UIState.swift (1)
copy(31-41)
SampoomManagement/Features/Part/UI/PartViewModel.swift (4)
SampoomManagement/Features/Part/UI/PartUIState.swift (1)
copy(43-61)SampoomManagement/Features/Part/Domain/UseCase/GetCategoryUseCase.swift (1)
execute(17-19)SampoomManagement/Features/Part/Domain/UseCase/GetGroupUseCase.swift (1)
execute(17-19)SampoomManagement/Features/Part/Domain/UseCase/GetPartUseCase.swift (1)
execute(17-19)
SampoomManagement/Features/Part/Domain/Models/PartsGroupList.swift (2)
SampoomManagement/Features/Part/Domain/Models/CategoryList.swift (1)
empty(19-21)SampoomManagement/Features/Part/Domain/Models/PartList.swift (1)
empty(19-21)
SampoomManagement/Features/Part/UI/PartView.swift (1)
SampoomManagement/Features/Part/UI/PartViewModel.swift (1)
onEvent(28-39)
🔇 Additional comments (6)
SampoomManagement/Features/Part/Domain/Models/PartsGroupList.swift (1)
10-22: LGTM!
PartsGroupList구현이 올바르며,CategoryList및PartList와 동일한 패턴을 따르고 있습니다. 코드가 명확하고 일관성이 있습니다.SampoomManagement/Features/Part/UI/PartListViewModel.swift (1)
18-71: 비동기 작업 관리가 올바르게 구현되었습니다.이전 리뷰에서 지적된 동시성 문제가 모두 해결되었습니다:
loadTask프로퍼티로 진행 중인 작업 추적- 새 작업 시작 전 이전 작업 취소
MainActor.run을 통한 명시적 UI 업데이트CancellationError처리구현이 견고하고 race condition 위험이 없습니다.
SampoomManagement/App/ContentView.swift (1)
141-156: 네비게이션 구조가 올바르게 구현되었습니다.
NavigationStack과navigationDestination을 사용한 계층적 네비게이션이 적절하게 설정되어 있습니다.navigationPath를 통한 상태 관리가 올바르며,PartListViewModel이 적절한 의존성과 함께 생성되고 있습니다.SampoomManagement/Features/Part/UI/PartView.swift (1)
10-149: 뷰 구조와 상태 관리가 잘 구현되었습니다.이전 리뷰의
onNavigateBack미사용 문제가 API 제거로 해결되었습니다. 현재 구현은:
- 명시적 의존성 주입으로 명확한 데이터 흐름 제공
- 로딩/에러/빈 상태를 적절하게 처리
CategoryItem및PartItemCard로 컴포넌트를 잘 분리구조가 깔끔하고 유지보수하기 좋습니다.
SampoomManagement/Features/Part/UI/PartViewModel.swift (1)
69-86: 비동기 함수 구조가 올바르게 수정되었습니다.이전 리뷰에서 지적된 불필요한
Task래퍼 문제가 해결되었습니다.loadGroup(categoryId:)가 이제 적절한 async 함수로 구현되어 있으며, line 65에서await로 호출되고 있습니다.SampoomManagement/Features/Part/UI/PartUIState.swift (1)
43-61:copy메서드의 이중 옵셔널 패턴이 올바르게 구현되었습니다.이전 리뷰에서 지적된 옵셔널 값 초기화 문제가 해결되었습니다.
String??및Category??타입을 사용하여:
nil전달 시 기존 값 유지.some(nil)전달 시 명시적으로 nil로 설정.some(value)전달 시 새 값으로 설정
??연산자가 이중 옵셔널과 함께 올바르게 작동하고 있습니다.
📝 Summary
부품 조회 화면 구현
🙏 Question & PR point
📬 Reference
Summary by CodeRabbit
새로운 기능
개선사항
주의
UI 변경