// // ViewController.swift // EmployeeDirectory // // Created by Matt Bruce on 1/20/25. // import UIKit import Combine class EmployeesViewController: UIViewController { private let tableView = UITableView() private let activityIndicator = UIActivityIndicatorView(style: .large) private let modeSegmentedControl = UISegmentedControl(items: EmployeeServiceMode.allCases.map{ $0.rawValue } ) private let viewModel = EmployeesViewModel() private var cancellables = Set() private var footerView: TableFooterView? public override func viewDidLoad() { super.viewDidLoad() setupUI() bindViewModel() viewModel.fetchEmployees() } private func setupUI() { view.backgroundColor = .white // Configure TableView tableView.register(EmployeeTableViewCell.self, forCellReuseIdentifier: EmployeeTableViewCell.identifier) tableView.dataSource = self view.addSubview(tableView) tableView.frame = view.bounds //add pull to refresh tableView.refreshControl = UIRefreshControl() tableView.refreshControl?.addTarget(self, action: #selector(didPullToRefresh), for: .valueChanged) // Configure Activity Indicator activityIndicator.center = view.center view.addSubview(activityIndicator) // Configure Mode Selector modeSegmentedControl.selectedSegmentIndex = 0 modeSegmentedControl.addTarget(self, action: #selector(onServiceModeChange), for: .valueChanged) navigationItem.titleView = modeSegmentedControl } private func bindViewModel() { viewModel.$employees .receive(on: RunLoop.main) .sink { [weak self] _ in self?.updateFooter() self?.tableView.reloadData() self?.tableView.refreshControl?.endRefreshing() } .store(in: &cancellables) viewModel.$isLoading .receive(on: RunLoop.main) .sink { [weak self] isLoading in if isLoading { self?.activityIndicator.startAnimating() } else { self?.activityIndicator.stopAnimating() } } .store(in: &cancellables) viewModel.$errorMessage .receive(on: RunLoop.main) .sink { [weak self] _ in self?.updateFooter() } .store(in: &cancellables) } private func updateFooter() { var message: String? { guard !viewModel.isLoading else { return nil } return viewModel.errorMessage ?? (viewModel.employees.isEmpty ? "No employees found, please try to refresh." : nil) } if let message, !viewModel.isLoading { // Lazy initialize footerView if needed if footerView == nil { footerView = TableFooterView(message: message) } else { // Update the message footerView?.update(message: message) } tableView.tableFooterView = footerView } else { tableView.tableFooterView = nil } } } extension EmployeesViewController { @objc private func didPullToRefresh() { viewModel.fetchEmployees() } @objc private func onServiceModeChange(_ sender: UISegmentedControl) { let selectedMode: EmployeeServiceMode switch sender.selectedSegmentIndex { case 0: selectedMode = .production case 1: selectedMode = .malformed case 2: selectedMode = .empty default: return } viewModel.changeMode(to: selectedMode) } } extension EmployeesViewController: UITableViewDataSource { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel.employees.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: EmployeeTableViewCell.identifier, for: indexPath) as? EmployeeTableViewCell else { return UITableViewCell() } let employee = viewModel.employees[indexPath.row] cell.configure(with: employee) return cell } }