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
29 changes: 24 additions & 5 deletions SampoomManagement/App/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ enum SettingNavigation: Hashable {
case settings
}

enum EmployeeNavigation: Hashable {
case employeeList
}

struct ContentView: View {
// MARK: - Properties
let dependencies: AppDependencies
Expand Down Expand Up @@ -43,7 +47,6 @@ struct ContentView: View {
// Dashboard 탭 (DashboardView directly)
Tab(value: .dashboard) {
NavigationStack(path: $dashboardNavigationPath) {
let user = try? dependencies.authPreferences.getStoredUser()
DashboardView(
viewModel: dashboardViewModel,
onLogoutClick: {
Expand All @@ -61,15 +64,16 @@ struct ContentView: View {
onSettingClick: {
dashboardNavigationPath.append(SettingNavigation.settings)
},
userName: user?.name ?? "",
branch: user?.branch ?? "",
userRole: user?.role ?? .user
onEmployeeClick: {
dashboardNavigationPath.append(EmployeeNavigation.employeeList)
}
)
.navigationDestination(for: SettingNavigation.self) { destination in
switch destination {
case .settings:
SettingView(
viewModel: dependencies.makeSettingViewModel(),
updateProfileViewModel: dependencies.makeUpdateProfileViewModel(),
onNavigateBack: {
if !dashboardNavigationPath.isEmpty {
dashboardNavigationPath.removeLast()
Expand All @@ -81,6 +85,21 @@ struct ContentView: View {
)
}
}
.navigationDestination(for: EmployeeNavigation.self) { destination in
switch destination {
case .employeeList:
EmployeeListView(
viewModel: dependencies.makeEmployeeListViewModel(),
editEmployeeViewModel: dependencies.makeEditEmployeeViewModel(),
updateEmployeeStatusViewModel: dependencies.makeUpdateEmployeeStatusViewModel(),
onNavigateBack: {
if !dashboardNavigationPath.isEmpty {
dashboardNavigationPath.removeLast()
}
}
)
}
}
}
} label: {
Label {
Expand Down Expand Up @@ -184,7 +203,7 @@ struct ContentView: View {
}
}
}
.accentColor(.accentColor)
.accentColor(.accent)
.tabViewStyle(.automatic)
}
}
Expand Down
69 changes: 61 additions & 8 deletions SampoomManagement/Core/DI/AppDependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ class AppDependencies {
let getDashboardUseCase: GetDashboardUseCase
let getWeeklySummaryUseCase: GetWeeklySummaryUseCase

// MARK: - User
let userAPI: UserAPI
let userRepository: UserRepository
let getProfileUseCase: GetProfileUseCase
let getStoredUserUseCase: GetStoredUserUseCase
let updateProfileUseCase: UpdateProfileUseCase
let getEmployeeUseCase: GetEmployeeUseCase
let editEmployeeUseCase: EditEmployeeUseCase
let getEmployeeCountUseCase: GetEmployeeCountUseCase
let updateEmployeeStatusUseCase: UpdateEmployeeStatusUseCase

init() {
// Global Message Handler
globalMessageHandler = GlobalMessageHandler.shared
Expand Down Expand Up @@ -152,16 +163,27 @@ class AppDependencies {
dashboardRepository = DashboardRepositoryImpl(api: dashboardAPI, authPreferences: authPreferences)
getDashboardUseCase = GetDashboardUseCase(repository: dashboardRepository)
getWeeklySummaryUseCase = GetWeeklySummaryUseCase(repository: dashboardRepository)

// User
userAPI = UserAPI(networkManager: networkManager)
userRepository = UserRepositoryImpl(api: userAPI, preferences: authPreferences)
getProfileUseCase = GetProfileUseCase(repository: userRepository)
getStoredUserUseCase = GetStoredUserUseCase(repository: userRepository)
updateProfileUseCase = UpdateProfileUseCase(repository: userRepository)
getEmployeeUseCase = GetEmployeeUseCase(repository: userRepository)
editEmployeeUseCase = EditEmployeeUseCase(repository: userRepository)
getEmployeeCountUseCase = GetEmployeeCountUseCase(repository: userRepository)
updateEmployeeStatusUseCase = UpdateEmployeeStatusUseCase(repository: userRepository)
}

// MARK: - ViewModel Factories

func makeLoginViewModel() -> LoginViewModel {
return LoginViewModel(loginUseCase: loginUseCase)
return LoginViewModel(loginUseCase: loginUseCase, getProfileUseCase: getProfileUseCase)
}

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

func makePartViewModel() -> PartViewModel {
Expand Down Expand Up @@ -229,19 +251,50 @@ class AppDependencies {
)
}

func makeDashboardViewModel() -> DashboardViewModel {
return DashboardViewModel(
getOrderUseCase: getOrderUseCase,
getDashboardUseCase: getDashboardUseCase,
getWeeklySummaryUseCase: getWeeklySummaryUseCase,
getStoredUserUseCase: getStoredUserUseCase,
getEmployeeCountUseCase: getEmployeeCountUseCase,
messageHandler: globalMessageHandler
)
}

func makeSettingViewModel() -> SettingViewModel {
return SettingViewModel(
authPreferences: authPreferences,
getStoredUserUseCase: getStoredUserUseCase,
signOutUseCase: signOutUseCase,
globalMessageHandler: globalMessageHandler
)
}

func makeDashboardViewModel() -> DashboardViewModel {
return DashboardViewModel(
getOrderUseCase: getOrderUseCase,
getDashboardUseCase: getDashboardUseCase,
getWeeklySummaryUseCase: getWeeklySummaryUseCase,
func makeEmployeeListViewModel() -> EmployeeListViewModel {
return EmployeeListViewModel(
getEmployeeUseCase: getEmployeeUseCase,
messageHandler: globalMessageHandler,
authPreferences: authPreferences
)
}

func makeEditEmployeeViewModel() -> EditEmployeeViewModel {
return EditEmployeeViewModel(
editEmployeeUseCase: editEmployeeUseCase,
messageHandler: globalMessageHandler
)
}

func makeUpdateEmployeeStatusViewModel() -> UpdateEmployeeStatusViewModel {
return UpdateEmployeeStatusViewModel(
updateEmployeeStatusUseCase: updateEmployeeStatusUseCase,
messageHandler: globalMessageHandler
)
}

func makeUpdateProfileViewModel() -> UpdateProfileViewModel {
return UpdateProfileViewModel(
updateProfileUseCase: updateProfileUseCase,
messageHandler: globalMessageHandler
)
}
Expand Down
30 changes: 30 additions & 0 deletions SampoomManagement/Core/Resources/StringResources.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,38 @@ struct StringResources {
struct Setting {
static let title = "설정"
static let editProfile = "프로필 수정"
static let editProfilePlaceholderUsername = "이름 입력"
static let editProfileEdited = "프로필 수정이 완료되었습니다."
static let logout = "로그아웃"
static let logoutTitle = "로그아웃"
static let dialogLogout = "로그아웃 하시겠습니까?"
static let userNotFound = "사용자 정보를 찾을 수 없습니다"
}

// MARK: - Employee
struct Employee {
static let title = "직원관리"
static let emptyEmployee = "직원이 없습니다."
static let email = "이메일"
static let createdAt = "최초생성일"
static let startedAt = "근무시작일"
static let endedAt = "근무종료일"
static let deletedAt = "퇴사일"
static let delete = "삭제"
static let edit = "수정"
static let editTitle = "직원 수정"
static let positionLabel = "직급"
static let editEdited = "직원 수정이 완료되었습니다."
static let editDeleted = "직원 삭제가 완료되었습니다."
static let statusLabel = "재직상태"
static let statusEdit = "재직상태 변경"
static let positionEdit = "직급 변경"
static let editStatusEdited = "직원 상태 수정이 완료되었습니다."
static let statusActive = "재직"
static let statusLeave = "휴직"
static let statusRetired = "퇴직"
static let employeeNotFound = "직원 정보를 찾을 수 없습니다"
static let userNotFound = "사용자 정보를 찾을 수 없습니다"
}

// MARK: - Auth
Expand Down
13 changes: 11 additions & 2 deletions SampoomManagement/Core/UI/Components/CommonButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import SwiftUI
enum ButtonType {
case filled // 채워진 버튼
case outlined // 테두리만 있는 버튼
case secondary // 톤 다운된 배경이 있는 버튼
}

// MARK: - Button Sizes
Expand Down Expand Up @@ -134,6 +135,8 @@ struct CommonButton: View {
return Color(.accent) // 기본 보라색
case .outlined:
return .clear
case .secondary:
return Color(.accent).opacity(0.3)
}
}

Expand All @@ -150,7 +153,9 @@ struct CommonButton: View {
case .filled:
return .white
case .outlined:
return borderColor ?? .blue
return borderColor ?? Color(.accent)
case .secondary:
return textColor ?? Color(.accent)
}
}

Expand All @@ -167,7 +172,9 @@ struct CommonButton: View {
case .filled:
return .clear
case .outlined:
return .blue
return borderColor ?? Color(.accent)
case .secondary:
return borderColor ?? Color(.accent)
}
}

Expand All @@ -177,6 +184,8 @@ struct CommonButton: View {
return 0
case .outlined:
return 1
case .secondary:
return 1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,3 @@ class AuthValidator {
}
}


15 changes: 15 additions & 0 deletions SampoomManagement/Core/Utilities/EmployeeStatusFormatter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// EmployeeStatusFormatter.swift
// SampoomManagement
//
// Created by Generated.
//

import Foundation

func employeeStatusToKorean(_ status: EmployeeStatus?) -> String {
guard let status else { return StringResources.Common.slash }
return status.displayNameKo
}


Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,3 @@ enum ValidationResult {
}
}


39 changes: 0 additions & 39 deletions SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,45 +27,6 @@ extension LoginResponseDTO {
}
}

extension GetProfileResponseDTO {
func toModel() -> User {
return User(
id: self.userId,
name: self.userName,
email: self.email,
role: UserRole(rawValue: self.role) ?? .user,
accessToken: "",
refreshToken: "",
expiresIn: 0,
position: UserPosition(rawValue: self.position) ?? .staff,
workspace: self.workspace,
branch: self.branch,
agencyId: self.organizationId,
startedAt: self.startedAt.isEmpty ? nil : self.startedAt,
endedAt: self.endedAt
)
}
}

extension User {
func mergeWith(profile: User) -> User {
return User(
id: self.id,
name: profile.name,
email: profile.email,
role: profile.role,
accessToken: self.accessToken,
refreshToken: self.refreshToken,
expiresIn: self.expiresIn,
position: profile.position,
workspace: profile.workspace,
branch: profile.branch,
agencyId: profile.agencyId,
startedAt: profile.startedAt,
endedAt: profile.endedAt
)
}
}

// MARK: - Vendors

Expand Down
10 changes: 0 additions & 10 deletions SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,5 @@ class AuthAPI {
responseType: RefreshResponseDTO.self
)
}

// 프로필 조회
func getProfile(workspace: String = "AGENCY") async throws -> APIResponse<GetProfileResponseDTO> {
return try await networkManager.request(
endpoint: "user/profile?workspace=\(workspace)",
method: .get,
parameters: nil,
responseType: GetProfileResponseDTO.self
)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -56,42 +56,15 @@ class AuthRepositoryImpl: AuthRepository {
throw AuthError.invalidResponse
}
let loginUser = loginDto.toModel()
// Store tokens immediately so that subsequent authorized calls (e.g., getProfile) carry Authorization header
// Store tokens immediately so that subsequent authorized calls carry Authorization header
do {
try preferences.saveToken(accessToken: loginUser.accessToken, refreshToken: loginUser.refreshToken)
try preferences.saveUser(loginUser)
} catch {
print("AuthRepositoryImpl - 초기 토큰 저장 실패: \(error)")
throw AuthError.tokenSaveFailed(error)
}

// 2) 프로필 조회 (서버 반영 지연 고려하여 재시도, 최종 실패 시 롤백)
let profileUser: User
do {
profileUser = try await retry(times: 5, initialDelayMs: 300, maxDelayMs: 1500, factor: 1.8) {
let profileResponse = try await self.api.getProfile()
guard let profileDto = profileResponse.data else {
throw AuthError.invalidResponse
}
return profileDto.toModel()
}
} catch {
// rollback tokens on any profile failure
preferences.clear()
throw error
}

// 3) 병합
let mergedUser = loginUser.mergeWith(profile: profileUser)

// 4) 저장
do {
try preferences.saveUser(mergedUser)
} catch {
print("AuthRepositoryImpl - 키체인 저장 실패: \(error)")
throw AuthError.tokenSaveFailed(error)
}

return mergedUser
return loginUser
}

func signOut() async throws {
Expand Down
Loading