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
2 changes: 1 addition & 1 deletion SampoomManagement/App/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct ContentView: View {
init(dependencies: AppDependencies) {
self.dependencies = dependencies
_partViewModel = StateObject(wrappedValue: dependencies.makePartViewModel())
_dashboardViewModel = StateObject(wrappedValue: DashboardViewModel(getOrderUseCase: dependencies.getOrderUseCase))
_dashboardViewModel = StateObject(wrappedValue: dependencies.makeDashboardViewModel())
}

// MARK: - Body
Expand Down
21 changes: 21 additions & 0 deletions SampoomManagement/Core/DI/AppDependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ class AppDependencies {
let receiveOrderUseCase: ReceiveOrderUseCase
let cancelOrderUseCase: CancelOrderUseCase

// MARK: - Dashboard
let dashboardAPI: DashboardAPI
let dashboardRepository: DashboardRepository
let getDashboardUseCase: GetDashboardUseCase
let getWeeklySummaryUseCase: GetWeeklySummaryUseCase

init() {
// Global Message Handler
globalMessageHandler = GlobalMessageHandler.shared
Expand Down Expand Up @@ -140,6 +146,12 @@ class AppDependencies {
completeOrderUseCase = CompleteOrderUseCase(repository: orderRepository)
receiveOrderUseCase = ReceiveOrderUseCase(repository: orderRepository)
cancelOrderUseCase = CancelOrderUseCase(repository: orderRepository)

// Dashboard
dashboardAPI = DashboardAPI(networkManager: networkManager)
dashboardRepository = DashboardRepositoryImpl(api: dashboardAPI, authPreferences: authPreferences)
getDashboardUseCase = GetDashboardUseCase(repository: dashboardRepository)
getWeeklySummaryUseCase = GetWeeklySummaryUseCase(repository: dashboardRepository)
}

// MARK: - ViewModel Factories
Expand Down Expand Up @@ -224,5 +236,14 @@ class AppDependencies {
globalMessageHandler: globalMessageHandler
)
}

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

3 changes: 3 additions & 0 deletions SampoomManagement/Core/Resources/StringResources.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ struct StringResources {
static let shortageOfParts = "부족 부품"
static let orderAmount = "주문 금액"
static let recentOrdersTitle = "최근 주문"
static let weeklySummaryTitle = "이번 주 요약"
static let weeklySummaryInStock = "입고 부품"
static let weeklySummaryOutStock = "출고 부품"
}

// MARK: - Tabs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation

extension DashboardResponseDTO {
func toModel() -> Dashboard {
return Dashboard(
totalParts: totalParts,
outOfStockParts: outOfStockParts,
lowStockParts: lowStockParts,
totalQuantity: totalQuantity
)
}
}

extension WeeklySummaryResponseDTO {
func toModel() -> WeeklySummary {
return WeeklySummary(
inStockParts: inStockParts,
outStockParts: outStockParts,
weekPeriod: weekPeriod
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation
import Alamofire

class DashboardAPI {
private let networkManager: NetworkManager

init(networkManager: NetworkManager) {
self.networkManager = networkManager
}

func getDashboard(agencyId: Int) async throws -> APIResponse<DashboardResponseDTO> {
let endpoint = "agency/\(agencyId)/dashboard"
return try await networkManager.request(
endpoint: endpoint,
method: .get,
responseType: DashboardResponseDTO.self
)
}

func getWeeklySummary(agencyId: Int) async throws -> APIResponse<WeeklySummaryResponseDTO> {
let endpoint = "agency/\(agencyId)/weekly-summary"
return try await networkManager.request(
endpoint: endpoint,
method: .get,
responseType: WeeklySummaryResponseDTO.self
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

struct DashboardResponseDTO: Codable {
let totalParts: Int
let outOfStockParts: Int
let lowStockParts: Int
let totalQuantity: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

struct WeeklySummaryResponseDTO: Codable {
let inStockParts: Int
let outStockParts: Int
let weekPeriod: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation

class DashboardRepositoryImpl: DashboardRepository {
private let api: DashboardAPI
private let authPreferences: AuthPreferences

init(api: DashboardAPI, authPreferences: AuthPreferences) {
self.api = api
self.authPreferences = authPreferences
}

func getDashboard() async throws -> Dashboard {
guard let user = try authPreferences.getStoredUser() else { throw NetworkError.unauthorized }
let response = try await api.getDashboard(agencyId: user.agencyId)
if !response.success { throw NetworkError.serverError(response.status, message: response.message) }
guard let data = response.data else { throw NetworkError.noData }
return data.toModel()
}

func getWeeklySummary() async throws -> WeeklySummary {
guard let user = try authPreferences.getStoredUser() else { throw NetworkError.unauthorized }
let response = try await api.getWeeklySummary(agencyId: user.agencyId)
if !response.success { throw NetworkError.serverError(response.status, message: response.message) }
guard let data = response.data else { throw NetworkError.noData }
return data.toModel()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

struct Dashboard: Equatable {
let totalParts: Int
let outOfStockParts: Int
let lowStockParts: Int
let totalQuantity: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

struct WeeklySummary: Equatable {
let inStockParts: Int
let outStockParts: Int
let weekPeriod: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

protocol DashboardRepository {
func getDashboard() async throws -> Dashboard
func getWeeklySummary() async throws -> WeeklySummary
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

struct GetDashboardUseCase {
private let repository: DashboardRepository

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

func execute() async throws -> Dashboard {
return try await repository.getDashboard()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

struct GetWeeklySummaryUseCase {
private let repository: DashboardRepository

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

func execute() async throws -> WeeklySummary {
return try await repository.getWeeklySummary()
}
}
26 changes: 23 additions & 3 deletions SampoomManagement/Features/Dashboard/UI/DashboardUiState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,48 @@ import Foundation

struct DashboardUiState: Equatable {
let orderList: [Order]
let dashboard: Dashboard?
let weeklySummary: WeeklySummary?
let dashboardLoading: Bool
let dashboardError: String?
let weeklySummaryLoading: Bool
let weeklySummaryError: String?

init(
orderList: [Order] = [],
dashboard: Dashboard? = nil,
weeklySummary: WeeklySummary? = nil,
dashboardLoading: Bool = false,
dashboardError: String? = nil
dashboardError: String? = nil,
weeklySummaryLoading: Bool = false,
weeklySummaryError: String? = nil
) {
self.orderList = orderList
self.dashboard = dashboard
self.weeklySummary = weeklySummary
self.dashboardLoading = dashboardLoading
self.dashboardError = dashboardError
self.weeklySummaryLoading = weeklySummaryLoading
self.weeklySummaryError = weeklySummaryError
}

func copy(
orderList: [Order]? = nil,
dashboard: Dashboard?? = nil,
weeklySummary: WeeklySummary?? = nil,
dashboardLoading: Bool? = nil,
dashboardError: String? = nil
dashboardError: String?? = nil,
weeklySummaryLoading: Bool? = nil,
weeklySummaryError: String?? = nil
) -> DashboardUiState {
return DashboardUiState(
orderList: orderList ?? self.orderList,
dashboard: dashboard ?? self.dashboard,
weeklySummary: weeklySummary ?? self.weeklySummary,
dashboardLoading: dashboardLoading ?? self.dashboardLoading,
dashboardError: dashboardError ?? self.dashboardError
dashboardError: dashboardError ?? self.dashboardError,
weeklySummaryLoading: weeklySummaryLoading ?? self.weeklySummaryLoading,
weeklySummaryError: weeklySummaryError ?? self.weeklySummaryError
)
Comment thread
Sangyoon98 marked this conversation as resolved.
}
}
Expand Down
60 changes: 47 additions & 13 deletions SampoomManagement/Features/Dashboard/UI/DashboardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct DashboardView: View {
titleSection
buttonSection
orderListSection
weeklySummarySection
Spacer(minLength: 100)
}
.padding(.horizontal, 16)
Expand Down Expand Up @@ -79,17 +80,18 @@ struct DashboardView: View {
}

private var buttonSection: some View {
VStack(spacing: 16) {
let dash = viewModel.uiState.dashboard
return VStack(spacing: 16) {
if userRole.isAdmin {
buttonCard(iconName: "employee", valueText: "45", subText: StringResources.Dashboard.employee, bordered: true) {}
Comment thread
Sangyoon98 marked this conversation as resolved.
}
HStack(spacing: 16) {
buttonCard(iconName: "parts", valueText: "1234", subText: StringResources.Dashboard.partsOnHand) {}
buttonCard(iconName: "orders", valueText: "23", subText: StringResources.Dashboard.partsInProgress) {}
buttonCard(iconName: "car", valueText: String(dash?.totalParts ?? 0), subText: StringResources.Dashboard.partsOnHand) {}
buttonCard(iconName: "block", valueText: String(dash?.outOfStockParts ?? 0), subText: StringResources.Dashboard.shortageOfParts) {}
}
HStack(spacing: 16) {
buttonCard(iconName: "warning", valueText: "19", subText: StringResources.Dashboard.shortageOfParts) {}
buttonCard(iconName: "money", valueText: "4,123,200", subText: StringResources.Dashboard.orderAmount) {}
buttonCard(iconName: "warning", valueText: String(dash?.lowStockParts ?? 0), subText: StringResources.Dashboard.shortageOfParts) {}
buttonCard(iconName: "parts", valueText: String(dash?.totalQuantity ?? 0), subText: StringResources.Dashboard.partsOnHand) {}
}
}
.padding(.bottom, 16)
Expand Down Expand Up @@ -127,6 +129,7 @@ struct DashboardView: View {
HStack {
Text(StringResources.Dashboard.recentOrdersTitle)
.font(.gmarketTitle2)
.fontWeight(.bold)
.foregroundColor(.text)
Spacer()
Button(action: { onNavigateOrderList() }) {
Expand All @@ -136,14 +139,7 @@ struct DashboardView: View {
}
}

if viewModel.uiState.dashboardLoading {
HStack { Spacer(); ProgressView(); Spacer() }
.padding(.vertical, 32)
} else if let error = viewModel.uiState.dashboardError {
VStack { Text(error).foregroundColor(.red) }
.frame(maxWidth: .infinity)
.padding(.vertical, 32)
} else if viewModel.uiState.orderList.isEmpty {
if viewModel.uiState.orderList.isEmpty {
VStack { Text(StringResources.Order.emptyList).foregroundColor(.textSecondary) }
.frame(maxWidth: .infinity)
.padding(.vertical, 32)
Expand All @@ -156,6 +152,44 @@ struct DashboardView: View {
}
}
}

private var weeklySummarySection: some View {
let weekly = viewModel.uiState.weeklySummary
return VStack(alignment: .leading, spacing: 16) {
Text(StringResources.Dashboard.weeklySummaryTitle)
.font(.gmarketTitle2)
.fontWeight(.bold)
.foregroundColor(.text)

HStack(spacing: 0) {
VStack(spacing: 8) {
Text(String(weekly?.inStockParts ?? 0))
.font(.gmarketTitle2)
.fontWeight(.bold)
.foregroundColor(.green)
Text(StringResources.Dashboard.weeklySummaryInStock)
.font(.gmarketBody)
.foregroundColor(.textSecondary)
}
.frame(maxWidth: .infinity)

VStack(spacing: 8) {
Text(String(weekly?.outStockParts ?? 0))
.font(.gmarketTitle2)
.fontWeight(.bold)
.foregroundColor(.red)
Text(StringResources.Dashboard.weeklySummaryOutStock)
.font(.gmarketBody)
.foregroundColor(.textSecondary)
}
.frame(maxWidth: .infinity)
}
}
.frame(maxWidth: .infinity)
.padding(16)
.background(Color.backgroundCard)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
}


Loading