Files
swift-estimated-pressures-core/Sources/PressureEstimationsFeature/EstimationsForm.swift

207 lines
5.1 KiB
Swift

import ComposableArchitecture
import SharedModels
import Styleguide
import SwiftUI
@Reducer
public struct EstimationForm {
public init() { }
@ObservableState
public struct State: Equatable, Sendable {
@SharedReader public var existingMeasurement: EquipmentMeasurement?
public let id: SharedPressureEstimationState.FlaggedEstimationContainer.ID?
public var airflowSelection: AirflowSelection
public var cfmTextField: Int?
public var coolingCapacity: EquipmentMetadata.CoolingCapacity
public var filterPressureDrop: Double?
public var name: String
public init(
id: SharedPressureEstimationState.FlaggedEstimationContainer.ID? = nil,
existingMeasurement: SharedReader<EquipmentMeasurement?>,
airflowSelection: AirflowSelection = .cfmPerTon,
cfmTextField: Int? = nil,
coolingCapacity: EquipmentMetadata.CoolingCapacity = .default,
filterPressureDrop: Double? = nil,
name: String = ""
) {
self.id = id
self._existingMeasurement = existingMeasurement
self.airflowSelection = airflowSelection
self.cfmTextField = cfmTextField
self.filterPressureDrop = filterPressureDrop
self.coolingCapacity = coolingCapacity
self.name = name
}
public var airflow: Double {
let cfmTextField = Double(self.cfmTextField ?? 0)
switch airflowSelection {
case .cfmPerTon:
return Double(cfmTextField) * coolingCapacity.rawValue
case .cfm:
return Double(cfmTextField)
}
}
public var isValid: Bool {
!name.isEmpty
&& cfmTextField != nil
}
// Note: Keep in display order of the picker.
public enum AirflowSelection: Hashable, CaseIterable, Identifiable, CustomStringConvertible {
case cfmPerTon
case cfm
init(
_ container: SharedPressureEstimationState.FlaggedEstimationContainer.EstimationState.CFMContainer
) {
switch container {
case .cfm:
self = .cfm
case .cfmPerTon:
self = .cfmPerTon
}
}
public var id: Self { self }
public var description: String {
switch self {
case .cfm:
return "CFM"
case .cfmPerTon:
return "CFM / Ton"
}
}
}
}
public enum Action: BindableAction {
case binding(BindingAction<State>)
}
public var body: some Reducer<State, Action> {
BindingReducer()
Reduce<State, Action> { state, action in
switch action {
case .binding:
return .none
}
}
}
}
public struct EstimationFormView: View {
@Bindable public var store: StoreOf<EstimationForm>
public var body: some View {
Form {
Section("Estimation Name") {
HStack {
TextLabel("Name")
.padding(.trailing, 40)
TextField(
"Name",
text: $store.name,
prompt: Text("Required")
)
}
}
Section("Airflow") {
VStack {
CaseIterablePicker(
"Airflow Type",
selection: $store.airflowSelection
)
.pickerStyle(.segmented)
Grid(alignment: .leading, horizontalSpacing: 40) {
if store.airflowSelection == .cfmPerTon {
GridRow {
HStack {
TextLabel("Capacity")
Spacer()
CoolingCapacityPicker(
selection: $store.coolingCapacity
)
}
.gridCellColumns(2)
}
}
GridRow {
HStack {
TextLabel(store.airflowSelection.description)
Spacer()
TextField(
"CFM / Ton",
value: $store.cfmTextField,
format: .number,
prompt: Text("CFM")
)
.frame(width: 100)
.numberPad()
}
.gridCellColumns(2)
}
}
}
}
if let existingsMeasurement = store.existingMeasurement,
existingsMeasurement.hasFilterDrop
{
Section("Filter Pressure Drop") {
HStack {
TextLabel("Pressure Drop")
Spacer()
TextField(
"Filter Drop",
value: $store.filterPressureDrop,
fractionLength: 2,
prompt: Text("Optional")
)
.frame(width: 100)
.decimalPad()
}
}
}
}
.labelsHidden()
.textLabelStyle(.boldSecondary)
.textFieldStyle(.roundedBorder)
}
}
fileprivate extension EquipmentMeasurement {
var hasFilterDrop: Bool {
switch self {
case let .airHandler(airHandler):
return airHandler.postFilterPressure > 0
case let .furnaceAndCoil(furnace):
return furnace.postFilterPressure > 0
}
}
}
#Preview {
EstimationFormView(
store: Store(
initialState: EstimationForm.State(
existingMeasurement: SharedReader(
Shared(EquipmentMeasurement.mock(type: .airHandler))
)
)
) {
EstimationForm()
}
)
}