Compare commits

...

3 Commits

Author SHA1 Message Date
bc635714b4 added sorting to the UI and ViewModels
Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
2025-01-21 12:42:10 -06:00
da6e68c219 updated naming
Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
2025-01-21 12:16:55 -06:00
95cbafec57 added sorting to viewmodel
Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
2025-01-21 12:12:44 -06:00
3 changed files with 96 additions and 17 deletions

View File

@ -9,8 +9,10 @@ import Foundation
public class MockEmployeeService: EmployeeServiceProtocol {
// MARK: - Properties
public static let shared = MockEmployeeService() // Default shared instance
private var employees: Employees
private var wrapper: Employees
private var sortField: EmployeeSortField = .fullName
private var sortOrder: EmployeeSortOrder = .ascending
// MARK: - Initializer
public init() {
@ -18,35 +20,47 @@ public class MockEmployeeService: EmployeeServiceProtocol {
guard let url = Bundle.main.url(forResource: jsonFileName, withExtension: "json"),
let data = try? Data(contentsOf: url),
let localData = try? JSONDecoder().decode(Employees.self, from: data) else {
employees = .init(employees: [], total: 0, page: 0, perPage: 0)
wrapper = .init(employees: [], total: 0, page: 0, perPage: 0)
return
}
employees = localData
wrapper = localData
sortEmployees()
}
public func getEmployees(_ serviceMode: EmployeeServiceMode) async throws -> Employees {
return employees
return wrapper
}
public func getEmployees(_ serviceMode: EmployeeServiceMode = .production,
page: Int, perPage: Int,
sortField: EmployeeSortField = .team,
sortField: EmployeeSortField = .fullName,
sortOrder: EmployeeSortOrder = .ascending) async throws -> Employees {
employees = .init(employees: employees.employees.sorted(by: sortField, with: sortOrder),
total: employees.employees.count,
page: page,
perPage: perPage)
let totalUsers = employees.employees.count
//resort mock data
if sortField != self.sortField || sortOrder != self.sortOrder {
self.sortField = sortField
self.sortOrder = sortOrder
sortEmployees()
}
let totalUsers = wrapper.employees.count
let startIndex = (page - 1) * perPage
let endIndex = min(startIndex + perPage, totalUsers)
guard startIndex < totalUsers else {
return .init(employees: [], total: totalUsers, page: page, perPage: perPage) // Return empty if out of bounds
}
let paginatedUsers = Array(employees.employees[startIndex..<endIndex])
return .init(employees: paginatedUsers, total: totalUsers, page: page, perPage: perPage)
//paged
let pagedEmployees = Array(wrapper.employees[startIndex..<endIndex])
return .init(employees: pagedEmployees, total: totalUsers, page: page, perPage: perPage)
}
private func sortEmployees() {
wrapper = .init(employees: wrapper.employees.sorted(by: sortField, with: sortOrder),
total: wrapper.employees.count,
page: 1,
perPage: 10)
}
}

View File

@ -68,6 +68,41 @@ class EmployeesViewController: UIViewController {
modeSegmentedControl.selectedSegmentIndex = 0
modeSegmentedControl.addTarget(self, action: #selector(onServiceModeChange), for: .valueChanged)
navigationItem.titleView = modeSegmentedControl
let sortButton = UIBarButtonItem(title: "Sort", style: .plain, target: self, action: #selector(showSortOptions))
navigationItem.rightBarButtonItem = sortButton
}
private func presentSortingActionSheet() {
let alert = UIAlertController(title: "Sort Employees", message: "Select a sort option", preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Full Name (Asc)", style: .default) { [weak self] _ in
self?.viewModel.sortField = .fullName
self?.viewModel.sortOrder = .ascending
})
alert.addAction(UIAlertAction(title: "Full Name (Desc)", style: .default) { [weak self] _ in
self?.viewModel.sortField = .fullName
self?.viewModel.sortOrder = .descending
})
alert.addAction(UIAlertAction(title: "Team (Asc)", style: .default) { [weak self] _ in
self?.viewModel.sortField = .team
self?.viewModel.sortOrder = .ascending
})
alert.addAction(UIAlertAction(title: "Team (Desc)", style: .default) { [weak self] _ in
self?.viewModel.sortField = .team
self?.viewModel.sortOrder = .descending
})
alert.addAction(UIAlertAction(title: "Employee Type (Asc)", style: .default) { [weak self] _ in
self?.viewModel.sortField = .employeeType
self?.viewModel.sortOrder = .ascending
})
alert.addAction(UIAlertAction(title: "Employee Type (Desc)", style: .default) { [weak self] _ in
self?.viewModel.sortField = .employeeType
self?.viewModel.sortOrder = .ascending
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
/// Using the ViewModel setup combine handlers
@ -154,6 +189,11 @@ extension EmployeesViewController: UITableViewDelegate {
// Mark: - Objective-C Methods
extension EmployeesViewController {
/// Show sort options
@objc private func showSortOptions() {
presentSortingActionSheet()
}
/// Fetch the Employees
@objc private func didPullToRefresh() {
viewModel.fetchEmployees()

View File

@ -6,23 +6,40 @@
//
import Foundation
import Combine
/// ViewModel that will be bound to an Employees model and used
/// specifically with the EmployeesViewController.
@MainActor
public class EmployeesViewModel: ObservableObject {
private var serviceMode: EmployeeServiceMode = .production
private var employeeService: EmployeeServiceProtocol = MockEmployeeService.shared
@Published public private(set) var employees: [Employee] = []
@Published public private(set) var errorMessage: String? = nil
@Published public private(set) var isLoading: Bool = false
@Published public private(set) var hasMorePages: Bool = true
@Published public var sortField: EmployeeSortField = .fullName
@Published public var sortOrder: EmployeeSortOrder = .ascending
private var cancellables = Set<AnyCancellable>()
private var currentPage = 1
private let perPage = 10
private var totalEmployees = 0
public init() {}
public init() {
observeSortingChanges()
}
/// Observe changes to sortField and sortOrder and debounce fetch calls
private func observeSortingChanges() {
Publishers.CombineLatest($sortField, $sortOrder)
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
.sink { [weak self] _, _ in
self?.resetAndFetchEmployees()
}
.store(in: &cancellables)
}
/// Fetch employees for the given page
public func fetchEmployees(page: Int = 1) {
@ -33,7 +50,10 @@ public class EmployeesViewModel: ObservableObject {
Task {
do {
// Fetch employees using the paginated API
let wrapper = try await MockEmployeeService.shared.getEmployees(.empty ,page: page, perPage: perPage)
let wrapper = try await employeeService.getEmployees(serviceMode, page: page,
perPage: perPage,
sortField: sortField,
sortOrder: sortOrder)
// Update published properties
if page == 1 {
@ -64,6 +84,11 @@ public class EmployeesViewModel: ObservableObject {
/// Change the service mode (e.g., production, malformed, empty)
public func changeMode(to mode: EmployeeServiceMode) {
serviceMode = mode
resetAndFetchEmployees()
}
/// Resets the current employee list and fetches data from page 1
private func resetAndFetchEmployees() {
currentPage = 1
employees = []
hasMorePages = true