feat: Begins equipment measurement form
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
import ComposableArchitecture
|
||||
import SharedModels
|
||||
import Styleguide
|
||||
import SwiftUI
|
||||
|
||||
@Reducer
|
||||
public struct EquipmentMeasurementForm {
|
||||
|
||||
public init() { }
|
||||
|
||||
@Reducer(state: .equatable)
|
||||
public enum Destination {
|
||||
case infoView(InfoViewFeature)
|
||||
}
|
||||
|
||||
@ObservableState
|
||||
public struct State: Equatable {
|
||||
@Presents public var destination: Destination.State?
|
||||
public var equipmentType: EquipmentType
|
||||
public var focusedField: Field?
|
||||
public var fields: FormFields
|
||||
|
||||
public init(
|
||||
destination: Destination.State? = nil,
|
||||
equipmentType: EquipmentType = .airHandler,
|
||||
focusedField: Field? = nil,
|
||||
fields: FormFields = .init()
|
||||
) {
|
||||
self.destination = destination
|
||||
self.equipmentType = equipmentType
|
||||
self.focusedField = focusedField
|
||||
self.fields = fields
|
||||
}
|
||||
|
||||
public var equipmentMeasurement: EquipmentMeasurement {
|
||||
fields.equipmentMeasurement(type: equipmentType)
|
||||
}
|
||||
|
||||
public var isValid: Bool {
|
||||
fields.airflow != nil
|
||||
&& fields.returnPlenumPressure != nil
|
||||
&& fields.postFilterPressure != nil
|
||||
&& fields.coilPressure != nil
|
||||
&& fields.supplyPlenumPressure != nil
|
||||
}
|
||||
|
||||
public struct FormFields: Equatable {
|
||||
public var airflow: Double?
|
||||
public var returnPlenumPressure: Double?
|
||||
public var postFilterPressure: Double?
|
||||
public var coilPressure: Double?
|
||||
public var supplyPlenumPressure: Double?
|
||||
|
||||
public init(
|
||||
airflow: Double? = nil,
|
||||
returnPlenumPressure: Double? = nil,
|
||||
postFilterPressure: Double? = nil,
|
||||
coilPressure: Double? = nil,
|
||||
supplyPlenumPressure: Double? = nil
|
||||
) {
|
||||
self.airflow = airflow
|
||||
self.returnPlenumPressure = returnPlenumPressure
|
||||
self.postFilterPressure = postFilterPressure
|
||||
self.coilPressure = coilPressure
|
||||
self.supplyPlenumPressure = supplyPlenumPressure
|
||||
}
|
||||
|
||||
public init(
|
||||
equipmentMeasurement: EquipmentMeasurement
|
||||
) {
|
||||
switch equipmentMeasurement {
|
||||
case let .airHandler(equipment):
|
||||
self.init(
|
||||
airflow: equipment.airflow,
|
||||
returnPlenumPressure: equipment.returnPlenumPressure,
|
||||
postFilterPressure: equipment.postFilterPressure,
|
||||
coilPressure: equipment.postCoilPressure,
|
||||
supplyPlenumPressure: equipment.supplyPlenumPressure
|
||||
)
|
||||
case let .furnaceAndCoil(equipment):
|
||||
self.init(
|
||||
airflow: equipment.airflow,
|
||||
returnPlenumPressure: equipment.returnPlenumPressure,
|
||||
postFilterPressure: equipment.postFilterPressure,
|
||||
coilPressure: equipment.preCoilPressure,
|
||||
supplyPlenumPressure: equipment.supplyPlenumPressure
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func equipmentMeasurement(type: EquipmentType) -> EquipmentMeasurement {
|
||||
switch type {
|
||||
|
||||
case .airHandler:
|
||||
return .airHandler(.init(
|
||||
airflow: airflow,
|
||||
returnPlenumPressure: returnPlenumPressure,
|
||||
postFilterPressure: postFilterPressure,
|
||||
postCoilPressure: coilPressure,
|
||||
supplyPlenumPressure: supplyPlenumPressure
|
||||
))
|
||||
|
||||
case .furnaceAndCoil:
|
||||
return .furnaceAndCoil(.init(
|
||||
airflow: airflow,
|
||||
returnPlenumPressure: returnPlenumPressure,
|
||||
postFilterPressure: postFilterPressure,
|
||||
preCoilPressure: coilPressure,
|
||||
supplyPlenumPressure: supplyPlenumPressure
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Field: Hashable, CaseIterable, FocusableField, Identifiable {
|
||||
case returnPlenumPressure
|
||||
case postFilterPressure
|
||||
case coilPressure
|
||||
case supplyPlenumPressure
|
||||
case airflow
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action: BindableAction, ViewAction {
|
||||
case binding(BindingAction<State>)
|
||||
case destination(PresentationAction<Destination.Action>)
|
||||
case view(View)
|
||||
|
||||
@CasePathable
|
||||
public enum View {
|
||||
case infoButtonTapped
|
||||
case resetButtonTapped
|
||||
case submitField
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some Reducer<State, Action> {
|
||||
BindingReducer()
|
||||
Reduce<State, Action> { state, action in
|
||||
switch action {
|
||||
|
||||
case .binding:
|
||||
return .none
|
||||
|
||||
case .destination:
|
||||
return .none
|
||||
|
||||
|
||||
case let .view(action):
|
||||
switch action {
|
||||
|
||||
case .infoButtonTapped:
|
||||
return .none
|
||||
|
||||
case .resetButtonTapped:
|
||||
state.fields = .init()
|
||||
return .none
|
||||
|
||||
case .submitField:
|
||||
state.focusedField = state.focusedField?.next
|
||||
return .none
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.ifLet(\.$destination, action: \.destination)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension Store where State == EquipmentMeasurementForm.State {
|
||||
|
||||
func prompt(
|
||||
field: EquipmentMeasurementForm.State.Field
|
||||
) -> String {
|
||||
let label = label(field: field)
|
||||
guard field != .airflow else { return label }
|
||||
return "\(label) Pressure"
|
||||
}
|
||||
|
||||
func label(
|
||||
field: EquipmentMeasurementForm.State.Field
|
||||
) -> String {
|
||||
switch field {
|
||||
case .returnPlenumPressure:
|
||||
return "Return"
|
||||
case .postFilterPressure:
|
||||
return "Post-Filter"
|
||||
case .coilPressure:
|
||||
switch state.equipmentType {
|
||||
case .airHandler:
|
||||
return "Post-Coil"
|
||||
case .furnaceAndCoil:
|
||||
return "Pre-Coil"
|
||||
}
|
||||
case .supplyPlenumPressure:
|
||||
return "Supply"
|
||||
case .airflow:
|
||||
return "Airflow"
|
||||
}
|
||||
}
|
||||
|
||||
var pressureFields: [EquipmentMeasurementForm.State.Field] {
|
||||
EquipmentMeasurementForm.State.Field.allCases.filter {
|
||||
$0 != .airflow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewAction(for: EquipmentMeasurementForm.self)
|
||||
public struct EquipmentMeasurementFormView: View {
|
||||
|
||||
@Bindable public var store: StoreOf<EquipmentMeasurementForm>
|
||||
|
||||
public init(store: StoreOf<EquipmentMeasurementForm>) {
|
||||
self.store = store
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
|
||||
} header: {
|
||||
Text("Equipment Type")
|
||||
} footer: {
|
||||
Picker("Equipment Type", selection: $store.equipmentType) {
|
||||
ForEach(EquipmentType.allCases) {
|
||||
Text($0.description)
|
||||
.tag($0)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.labelsHidden()
|
||||
}
|
||||
|
||||
Section {
|
||||
Grid(alignment: .leading, horizontalSpacing: 40) {
|
||||
ForEach(store.pressureFields) { field in
|
||||
GridRow {
|
||||
TextLabel(store.label(field: field))
|
||||
textField(for: field)
|
||||
}
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
HStack {
|
||||
Text("Static Measurements")
|
||||
Spacer()
|
||||
InfoButton { send(.infoButtonTapped) }
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Grid(alignment: .leading, horizontalSpacing: 60) {
|
||||
GridRow {
|
||||
TextLabel(store.label(field: .airflow))
|
||||
textField(for: .airflow)
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
HStack {
|
||||
Spacer()
|
||||
ResetButton { send(.resetButtonTapped) }
|
||||
.padding(.top)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.textLabelStyle(.boldSecondary)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
|
||||
private func textField(
|
||||
for field: EquipmentMeasurementForm.State.Field
|
||||
) -> some View {
|
||||
let value: Binding<Double?>
|
||||
var fractionLength: Int = 2
|
||||
|
||||
switch field {
|
||||
case .returnPlenumPressure:
|
||||
value = $store.fields.returnPlenumPressure
|
||||
case .postFilterPressure:
|
||||
value = $store.fields.postFilterPressure
|
||||
case .coilPressure:
|
||||
value = $store.fields.coilPressure
|
||||
case .supplyPlenumPressure:
|
||||
value = $store.fields.supplyPlenumPressure
|
||||
case .airflow:
|
||||
value = $store.fields.airflow
|
||||
fractionLength = 0
|
||||
}
|
||||
|
||||
return textField(
|
||||
title: store.prompt(field: field),
|
||||
value: value,
|
||||
fractionLength: fractionLength,
|
||||
numberPad: field == .airflow
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func textField(
|
||||
title: String,
|
||||
value: Binding<Double?>,
|
||||
fractionLength: Int,
|
||||
numberPad: Bool
|
||||
) -> some View {
|
||||
if numberPad {
|
||||
TextField(
|
||||
title,
|
||||
value: value,
|
||||
fractionLength: fractionLength,
|
||||
prompt: Text(title)
|
||||
)
|
||||
.numberPad()
|
||||
} else {
|
||||
TextField(
|
||||
title,
|
||||
value: value,
|
||||
fractionLength: fractionLength,
|
||||
prompt: Text(title)
|
||||
)
|
||||
.decimalPad()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
EquipmentMeasurementFormView(
|
||||
store: Store(initialState: EquipmentMeasurementForm.State()) {
|
||||
EquipmentMeasurementForm()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
public enum EquipmentType: Equatable, CaseIterable, CustomStringConvertible {
|
||||
public enum EquipmentType: Equatable, CaseIterable, CustomStringConvertible, Identifiable {
|
||||
case airHandler
|
||||
case furnaceAndCoil
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .airHandler:
|
||||
|
||||
@@ -16,4 +16,18 @@ extension TextField where Label == Text {
|
||||
)
|
||||
}
|
||||
|
||||
public init<S: StringProtocol>(
|
||||
_ titleKey: S,
|
||||
value: Binding<Double?>,
|
||||
fractionLength: Int,
|
||||
prompt: Text? = nil
|
||||
) {
|
||||
self.init(
|
||||
titleKey,
|
||||
value: value,
|
||||
format: .number.precision(.fractionLength(fractionLength)),
|
||||
prompt: prompt
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user