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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion SampoomManagement/Core/DI/AppDependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class AppDependencies {
let checkLoginStateUseCase: CheckLoginStateUseCase
let signOutUseCase: SignOutUseCase
let clearTokensUseCase: ClearTokensUseCase
let getVendorUseCase: GetVendorUseCase
let authViewModel: AuthViewModel

// MARK: - Network Auth
Expand Down Expand Up @@ -94,6 +95,7 @@ class AppDependencies {
checkLoginStateUseCase = CheckLoginStateUseCase(repository: authRepository)
signOutUseCase = SignOutUseCase(repository: authRepository)
clearTokensUseCase = ClearTokensUseCase(repository: authRepository)
getVendorUseCase = GetVendorUseCase(repository: authRepository)

// Auth ViewModel
authViewModel = AuthViewModel(
Expand Down Expand Up @@ -147,7 +149,7 @@ class AppDependencies {
}

func makeSignUpViewModel() -> SignUpViewModel {
return SignUpViewModel(signUpUseCase: signUpUseCase)
return SignUpViewModel(signUpUseCase: signUpUseCase, getVendorUseCase: getVendorUseCase)
}

func makePartViewModel() -> PartViewModel {
Expand Down
5 changes: 3 additions & 2 deletions SampoomManagement/Core/Resources/StringResources.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ struct StringResources {

// MARK: - Outbound
struct Outbound {
static let title = "출고"
static let title = "출고목록"
static let emptyAll = "비우기"
static let processOrder = "부품 출고처리"
static let orderSuccess = "출고 주문 성공"
Expand All @@ -120,7 +120,7 @@ struct StringResources {
static let title = "장바구니"
static let emptyAll = "비우기"
static let processOrder = "부품 주문"
static let orderSuccess = "주문 성공!"
static let orderSuccess = "주문이 완료되었습니다"
static let updateQuantityError = "수량 업데이트 에러"
static let deleteError = "삭제 에러"
static let confirmProcessTitle = "주문 확인"
Expand Down Expand Up @@ -172,6 +172,7 @@ struct StringResources {
static let detailOrderReceive = "입고처리"
static let detailDialogOrderReceive = "입고 처리하시겠습니까?"
static let detailToastOrderReceive = "입고 처리되었습니다"
static let detailTotalAmount = "총 가격"

// Order Status
static let statusPending = "대기중"
Expand Down
5 changes: 4 additions & 1 deletion SampoomManagement/Core/UI/Components/OrderItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ struct OrderItem: View {
}
Spacer(minLength: 12)
VStack(alignment: .trailing, spacing: 6) {
Text(order.createdAt ?? "-")
Text(order.createdAt.map { DateFormatterUtil.formatDate($0) } ?? "-")
.font(.gmarketCaption)
.foregroundColor(.textSecondary)
StatusChip(status: order.status)
Text(formatWon(order.totalCost))
.font(.gmarketBody)
.foregroundColor(.text)
}
}
.padding(16)
Expand Down
18 changes: 18 additions & 0 deletions SampoomManagement/Core/Utilities/CurrencyFormatter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// CurrencyFormatter.swift
// SampoomManagement
//
// Simple KRW formatter: 12,345원
//

import Foundation

func formatWon(_ value: Int) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "ko_KR")
let number = NSNumber(value: value)
return (formatter.string(from: number) ?? "\(value)") + "원"
}


16 changes: 16 additions & 0 deletions SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,19 @@ extension User {
)
}
}

// MARK: - Vendors

extension GetVendorsResponseDTO {
func toModel() -> Vendor {
return Vendor(
id: id,
vendorCode: vendorCode,
name: name,
businessNumber: businessNumber,
ceoName: ceoName,
address: address,
status: status
)
}
}
9 changes: 9 additions & 0 deletions SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ class AuthAPI {
responseType: EmptyResponse.self
)
}

// 벤더 목록 조회
func getVendors() async throws -> APIResponse<[GetVendorsResponseDTO]> {
return try await networkManager.request(
endpoint: "site/vendors",
method: .get,
responseType: [GetVendorsResponseDTO].self
)
}

// 토큰 재발급
func refresh(refreshToken: String) async throws -> APIResponse<RefreshResponseDTO> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// GetVendorsResponseDTO.swift
// SampoomManagement
//
// Created to mirror Android vendor list API.
//

import Foundation

struct GetVendorsResponseDTO: Codable {
let id: Int
let vendorCode: String
let name: String
let businessNumber: String
let ceoName: String
let address: String
let status: String
}


Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,20 @@ class AuthRepositoryImpl: AuthRepository {
userName: userName,
position: position
)

// 회원가입 후 자동 로그인
return try await signIn(email: email, password: password)
// 회원가입 직후 DB 반영 지연을 고려하여 잠시 대기 후 재시도하며 로그인
// 0.5초 대기 후 최대 5회 재시도 (지수 백오프)
try await Task.sleep(nanoseconds: 500_000_000)
var currentDelayMs: UInt64 = 500
for attempt in 1...5 {
do {
return try await signIn(email: email, password: password)
} catch {
if attempt == 5 { throw error }
try await Task.sleep(nanoseconds: currentDelayMs * 1_000_000)
currentDelayMs = min(currentDelayMs &* 2, 2_000)
}
}
throw AuthError.invalidResponse
}

func signIn(email: String, password: String) async throws -> User {
Expand Down Expand Up @@ -153,6 +164,16 @@ class AuthRepositoryImpl: AuthRepository {
func getRefreshToken() throws -> String? {
return try preferences.getRefreshToken()
}

// MARK: - Vendors
func getVendorList() async throws -> VendorList {
let response = try await api.getVendors()
if !response.success {
throw NetworkError.serverError(response.status, message: response.message)
}
let items = (response.data ?? []).map { $0.toModel() }
return VendorList(items: items)
}
}

// MARK: - Retry Helper (Exponential Backoff)
Expand All @@ -165,7 +186,7 @@ private func retry<T>(
) async throws -> T {
precondition(times >= 1)
var currentDelayMs = initialDelayMs
for attempt in 1..<(times) {
for _ in 1..<(times) {
do {
return try await block()
} catch {
Expand Down
20 changes: 20 additions & 0 deletions SampoomManagement/Features/Auth/Domain/Models/Vendor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Vendor.swift
// SampoomManagement
//
// Created to mirror Android vendor domain model.
//

import Foundation

struct Vendor: Equatable, Identifiable {
let id: Int
let vendorCode: String
let name: String
let businessNumber: String
let ceoName: String
let address: String
let status: String
}


17 changes: 17 additions & 0 deletions SampoomManagement/Features/Auth/Domain/Models/VendorList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// VendorList.swift
// SampoomManagement
//
// Created to mirror Android vendor list domain model.
//

import Foundation

struct VendorList: Equatable {
let items: [Vendor]
var totalCount: Int { items.count }
var isEmpty: Bool { items.isEmpty }
static func empty() -> VendorList { VendorList(items: []) }
}


Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ protocol AuthRepository {
// 토큰 조회 (API 요청 시 사용)
func getAccessToken() throws -> String?
func getRefreshToken() throws -> String?

// 벤더 목록 조회
func getVendorList() async throws -> VendorList
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// GetVendorUseCase.swift
// SampoomManagement
//
// Mirrors Android GetVendorUseCase.
//

import Foundation

struct GetVendorUseCase {
private let repository: AuthRepository

init(repository: AuthRepository) {
self.repository = repository
}

func execute() async throws -> VendorList {
return try await repository.getVendorList()
}
}


17 changes: 17 additions & 0 deletions SampoomManagement/Features/Auth/UI/SignUpUiState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ struct SignUpUiState: UIState {
let password: String
let passwordCheck: String

// Vendor
let vendors: [Vendor]
let selectedVendor: Vendor?
let vendorsLoading: Bool

// Error message
let nameError: String?
let branchError: String?
Expand All @@ -36,6 +41,9 @@ struct SignUpUiState: UIState {
email: String = "",
password: String = "",
passwordCheck: String = "",
vendors: [Vendor] = [],
selectedVendor: Vendor? = nil,
vendorsLoading: Bool = false,
nameError: String? = nil,
branchError: String? = nil,
positionError: String? = nil,
Expand All @@ -53,6 +61,9 @@ struct SignUpUiState: UIState {
self.email = email
self.password = password
self.passwordCheck = passwordCheck
self.vendors = vendors
self.selectedVendor = selectedVendor
self.vendorsLoading = vendorsLoading
self.nameError = nameError
self.branchError = branchError
self.positionError = positionError
Expand Down Expand Up @@ -87,6 +98,9 @@ struct SignUpUiState: UIState {
email: String? = nil,
password: String? = nil,
passwordCheck: String? = nil,
vendors: [Vendor]? = nil,
selectedVendor: Vendor?? = nil,
vendorsLoading: Bool? = nil,
nameError: String?? = nil,
branchError: String?? = nil,
positionError: String?? = nil,
Expand All @@ -105,6 +119,9 @@ struct SignUpUiState: UIState {
email: email ?? self.email,
password: password ?? self.password,
passwordCheck: passwordCheck ?? self.passwordCheck,
vendors: vendors ?? self.vendors,
selectedVendor: selectedVendor ?? self.selectedVendor,
vendorsLoading: vendorsLoading ?? self.vendorsLoading,
nameError: nameError ?? self.nameError,
branchError: branchError ?? self.branchError,
positionError: positionError ?? self.positionError,
Expand Down
54 changes: 45 additions & 9 deletions SampoomManagement/Features/Auth/UI/SignUpView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct SignUpView: View {
@State private var email = ""
@State private var password = ""
@State private var passwordCheck = ""
@State private var showVendorSheet = false
@FocusState private var focusedField: Field?

let onSuccess: () -> Void
Expand Down Expand Up @@ -64,15 +65,50 @@ struct SignUpView: View {
.font(.gmarketBody)
.foregroundColor(Color("Text"))
.padding(.bottom, 4)
CommonTextField(
value: $branch,
placeholder: StringResources.Auth.branchPlaceholder,
isError: viewModel.uiState.branchError != nil,
errorMessage: viewModel.uiState.branchError,
onTextChange: { text in viewModel.updateBranch(text) },
submitLabel: .next,
onSubmit: { focusedField = .position }
)
Button(action: { showVendorSheet = true }) {
HStack {
Text(viewModel.uiState.selectedVendor?.name ?? StringResources.Auth.branchPlaceholder)
.font(.gmarketBody)
.foregroundColor(viewModel.uiState.selectedVendor == nil ? Color.gray : Color("Text"))
Spacer()
Image(systemName: "chevron.down")
.foregroundColor(Color.gray)
}
.padding(EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16))
.padding(4)
.background(
RoundedRectangle(cornerRadius: 16)
.stroke(viewModel.uiState.branchError != nil ? Color.red : Color.gray.opacity(0.4), lineWidth: 1)
)
}
.sheet(isPresented: $showVendorSheet) {
NavigationStack {
List(viewModel.uiState.vendors, id: \.id) { vendor in
Button(action: {
viewModel.selectVendor(vendor)
showVendorSheet = false
}) {
VStack(alignment: .leading, spacing: 2) {
Text(vendor.name)
.foregroundColor(Color("Text"))
Text(vendor.vendorCode)
.font(.gmarketCaption)
.foregroundColor(Color("TextSecondary"))
}
}
}
.navigationTitle(StringResources.Auth.branchLabel)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(StringResources.Common.close) { showVendorSheet = false }
}
}
}
}
.onChange(of: viewModel.uiState.selectedVendor) { _, newValue in
branch = newValue?.name ?? ""
if let b = newValue?.name { viewModel.updateBranch(b) }
}
.focused($focusedField, equals: .branch)

Spacer()
Expand Down
Loading