feat: Adding improvements to pressure estimations feature.

This commit is contained in:
2024-06-12 10:29:33 -04:00
parent da8a8638c7
commit 9a145b3290
7 changed files with 239 additions and 89 deletions

View File

@@ -304,9 +304,14 @@ public struct EquipmentMeasurementFormView: View {
} }
Section { Section {
Grid(alignment: .leading, horizontalSpacing: 60) { Grid(alignment: .leading, horizontalSpacing: 80) {
gridRow(for: .airflow) gridRow(for: .airflow)
} }
} header: {
HStack {
Spacer()
Text(store.sharedSettings.equipmentMetadata.coolingCapacity.description)
}
} footer: { } footer: {
HStack { HStack {
Spacer() Spacer()

View File

@@ -355,45 +355,6 @@ fileprivate enum RatingsField: String, Hashable, CaseIterable, Identifiable {
var prompt: String { "\(label) Pressure" } var prompt: String { "\(label) Pressure" }
} }
//fileprivate extension Store where State == EquipmentSettingsForm.State {
//
//
// func label(for ratingsField: RatingsField) -> String {
// self.label(for: ratingsField.field)
// }
//
// func prompt(for ratingsField: RatingsField) -> String {
// self.prompt(for: ratingsField.field)
// }
//
// func label(for field: EquipmentSettingsForm.State.Field) -> String {
// switch field {
// case .heatingCapacity:
// return "Heating"
// case .minimumStaticPressure:
// return "Minimum"
// case .maximumStaticPressure:
// return "Maximum"
// case .ratedStaticPressure:
// return "Rated"
// case .manufacturersIncludedFilterPressureDrop:
// return "Filter Drop"
// }
// }
//
//
// func prompt(for field: EquipmentSettingsForm.State.Field) -> String {
// switch field {
// case .heatingCapacity:
// return "Heating Capacity"
// case .minimumStaticPressure, .maximumStaticPressure, .ratedStaticPressure:
// return "\(label(for: field)) Pressure"
// case .manufacturersIncludedFilterPressureDrop:
// return label(for: field)
// }
// }
//}
#Preview { #Preview {
NavigationStack { NavigationStack {
EquipmentSettingsFormView( EquipmentSettingsFormView(

View File

@@ -3,35 +3,82 @@ import SharedModels
import Styleguide import Styleguide
import SwiftUI import SwiftUI
#warning("Use shared settings, don't display filter pressure drop if current flagged measurement pressure drop is not set.")
@Reducer @Reducer
public struct EstimationForm { public struct EstimationForm {
public init() { } public init() { }
@ObservableState @ObservableState
public struct State: Equatable, Sendable { public struct State: Equatable, Sendable {
public var cfmPerTon: Int @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 coolingCapacity: EquipmentMetadata.CoolingCapacity
public var filterPressureDrop: Double? public var filterPressureDrop: Double?
public var name: String public var name: String
public init( public init(
cfmPerTon: Int = 350, id: SharedPressureEstimationState.FlaggedEstimationContainer.ID? = nil,
existingMeasurement: SharedReader<EquipmentMeasurement?>,
airflowSelection: AirflowSelection = .cfmPerTon,
cfmTextField: Int? = nil,
coolingCapacity: EquipmentMetadata.CoolingCapacity = .default, coolingCapacity: EquipmentMetadata.CoolingCapacity = .default,
filterPressureDrop: Double? = nil, filterPressureDrop: Double? = nil,
name: String = "" name: String = ""
) { ) {
self.cfmPerTon = cfmPerTon self.id = id
self._existingMeasurement = existingMeasurement
self.airflowSelection = airflowSelection
self.cfmTextField = cfmTextField
self.filterPressureDrop = filterPressureDrop self.filterPressureDrop = filterPressureDrop
self.coolingCapacity = coolingCapacity self.coolingCapacity = coolingCapacity
self.name = name self.name = name
} }
public var airflow: Double { public var airflow: Double {
Double(cfmPerTon) * coolingCapacity.rawValue 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 var isValid: Bool { !name.isEmpty }
} }
public enum Action: BindableAction { public enum Action: BindableAction {
@@ -68,7 +115,15 @@ public struct EstimationFormView: View {
} }
} }
Section("Airflow") { Section("Airflow") {
VStack {
CaseIterablePicker(
"Airflow Type",
selection: $store.airflowSelection
)
.pickerStyle(.segmented)
Grid(alignment: .leading, horizontalSpacing: 40) { Grid(alignment: .leading, horizontalSpacing: 40) {
if store.airflowSelection == .cfmPerTon {
GridRow { GridRow {
HStack { HStack {
TextLabel("Capacity") TextLabel("Capacity")
@@ -79,13 +134,14 @@ public struct EstimationFormView: View {
} }
.gridCellColumns(2) .gridCellColumns(2)
} }
}
GridRow { GridRow {
HStack { HStack {
TextLabel("CFM / Ton") TextLabel(store.airflowSelection.description)
Spacer() Spacer()
TextField( TextField(
"CFM / Ton", "CFM / Ton",
value: $store.cfmPerTon, value: $store.cfmTextField,
format: .number, format: .number,
prompt: Text("CFM") prompt: Text("CFM")
) )
@@ -96,7 +152,11 @@ public struct EstimationFormView: View {
} }
} }
} }
}
if let existingsMeasurement = store.existingMeasurement,
existingsMeasurement.hasFilterDrop
{
Section("Filter Pressure Drop") { Section("Filter Pressure Drop") {
HStack { HStack {
TextLabel("Pressure Drop") TextLabel("Pressure Drop")
@@ -112,15 +172,34 @@ public struct EstimationFormView: View {
} }
} }
} }
}
.labelsHidden() .labelsHidden()
.textLabelStyle(.boldSecondary) .textLabelStyle(.boldSecondary)
.textFieldStyle(.roundedBorder) .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 { #Preview {
EstimationFormView( EstimationFormView(
store: Store(initialState: EstimationForm.State()) { store: Store(
initialState: EstimationForm.State(
existingMeasurement: SharedReader(
Shared(EquipmentMeasurement.mock(type: .airHandler))
)
)
) {
EstimationForm() EstimationForm()
} }
) )

View File

@@ -57,8 +57,9 @@ public struct FlaggedMeasurementsList: Sendable {
@CasePathable @CasePathable
public enum View { public enum View {
case addButtonTapped case addButtonTapped
case deleteEstimationButtonTapped(id: SharedPressureEstimationState.FlaggedEstimationContainer.ID)
case destination(DestinationAction) case destination(DestinationAction)
case editButtonTapped(id: SharedPressureEstimationState.FlaggedMeasurementContainer.ID) case editButtonTapped(id: SharedPressureEstimationState.FlaggedEstimationContainer.ID)
case onAppear case onAppear
@CasePathable @CasePathable
@@ -107,10 +108,15 @@ public struct FlaggedMeasurementsList: Sendable {
case .addButtonTapped: case .addButtonTapped:
state.destination = .estimationForm(.init( state.destination = .estimationForm(.init(
existingMeasurement: state.$sharedSettings.equipmentMeasurement,
coolingCapacity: state.sharedSettings.equipmentMetadata.coolingCapacity coolingCapacity: state.sharedSettings.equipmentMetadata.coolingCapacity
)) ))
return .none return .none
case let .deleteEstimationButtonTapped(id: id):
state.sharedSettings.flaggedEstimations.remove(id: id)
return .none
case let .destination(action): case let .destination(action):
switch action { switch action {
case .cancelButtonTapped: case .cancelButtonTapped:
@@ -237,7 +243,16 @@ public struct FlaggedMeasurementListView: View {
HStack { HStack {
Text(measurement.name) Text(measurement.name)
Spacer() Spacer()
Button("Edit") { send(.editButtonTapped(id: measurement.id)) } Menu {
EditButton { send(.editButtonTapped(id: measurement.id)) }
DeleteButton { send(.deleteEstimationButtonTapped(id: measurement.id)) }
} label: {
Label("Actions", systemImage: "list.dash")
} primaryAction: {
send(.editButtonTapped(id: measurement.id))
}
.labelStyle(.iconOnly)
.menuStyle(ButtonMenuStyle())
} }
} }
} }
@@ -282,7 +297,7 @@ public struct FlaggedMeasurementListView: View {
#if DEBUG #if DEBUG
private let budgets = BudgetedPercentEnvelope(equipmentType: .airHandler, fanType: .constantSpeed) private let budgets = BudgetedPercentEnvelope(equipmentType: .airHandler, fanType: .constantSpeed)
private let flaggedMeasurements = IdentifiedArrayOf<SharedPressureEstimationState.FlaggedMeasurementContainer>( private let flaggedMeasurements = IdentifiedArrayOf<SharedPressureEstimationState.FlaggedEstimationContainer>(
uniqueElements: [ uniqueElements: [
.init( .init(
id: UUID(0), id: UUID(0),

View File

@@ -9,7 +9,7 @@ public struct SharedPressureEstimationState: Equatable, Sendable {
public var equipmentMeasurement: EquipmentMeasurement? public var equipmentMeasurement: EquipmentMeasurement?
public var equipmentMetadata: EquipmentMetadata public var equipmentMetadata: EquipmentMetadata
public var flaggedEquipmentMeasurement: EquipmentMeasurement.FlaggedMeasurement? public var flaggedEquipmentMeasurement: EquipmentMeasurement.FlaggedMeasurement?
public var flaggedEstimations: IdentifiedArrayOf<FlaggedMeasurementContainer> public var flaggedEstimations: IdentifiedArrayOf<FlaggedEstimationContainer>
public var heatingCapacity: Double? public var heatingCapacity: Double?
public var manufacturersIncludedFilterPressureDrop: Double? public var manufacturersIncludedFilterPressureDrop: Double?
@@ -18,7 +18,7 @@ public struct SharedPressureEstimationState: Equatable, Sendable {
equipmentMeasurement: EquipmentMeasurement? = nil, equipmentMeasurement: EquipmentMeasurement? = nil,
equipmentMetadata: EquipmentMetadata = .init(), equipmentMetadata: EquipmentMetadata = .init(),
flaggedEquipmentMeasurement: EquipmentMeasurement.FlaggedMeasurement? = nil, flaggedEquipmentMeasurement: EquipmentMeasurement.FlaggedMeasurement? = nil,
flaggedEstimations: IdentifiedArrayOf<FlaggedMeasurementContainer> = [], flaggedEstimations: IdentifiedArrayOf<FlaggedEstimationContainer> = [],
heatingCapacity: Double? = nil, heatingCapacity: Double? = nil,
manufacturersIncludedFilterPressureDrop: Double? = nil manufacturersIncludedFilterPressureDrop: Double? = nil
) { ) {
@@ -36,7 +36,8 @@ public struct SharedPressureEstimationState: Equatable, Sendable {
set { equipmentMetadata[keyPath: keyPath] = newValue } set { equipmentMetadata[keyPath: keyPath] = newValue }
} }
public struct FlaggedMeasurementContainer: Equatable, Identifiable, Sendable { #warning("Needs to hold onto estimation state, so it can be editable")
public struct FlaggedEstimationContainer: Equatable, Identifiable, Sendable {
public let id: UUID public let id: UUID
public var flaggedMeasurement: EquipmentMeasurement.FlaggedMeasurement public var flaggedMeasurement: EquipmentMeasurement.FlaggedMeasurement
public var name: String public var name: String
@@ -50,6 +51,44 @@ public struct SharedPressureEstimationState: Equatable, Sendable {
self.name = name self.name = name
self.flaggedMeasurement = flaggedMeasurement self.flaggedMeasurement = flaggedMeasurement
} }
public struct EstimationState: Equatable, Sendable {
public var cfm: CFMContainer
public var filterPressureDrop: Double?
public var name: String?
public init(
cfm: CFMContainer,
filterPressureDrop: Double? = nil,
name: String? = nil
) {
self.cfm = cfm
self.filterPressureDrop = filterPressureDrop
self.name = name
}
var displayName: String {
guard let name else {
return "@\(Int(cfm.airflow)) CFM"
}
return name
}
public enum CFMContainer: Equatable, Sendable {
case cfm(Int)
case cfmPerTon(Int, EquipmentMetadata.CoolingCapacity)
var airflow: Double {
switch self {
case let .cfm(cfm):
return Double(cfm)
case let .cfmPerTon(cfmPerTon, capacity):
return Double(cfmPerTon) * capacity.rawValue
}
}
}
}
} }
} }

View File

@@ -1,5 +1,19 @@
import SwiftUI import SwiftUI
public struct DeleteButton: View {
let action: () -> Void
public init(action: @escaping () -> Void) {
self.action = action
}
public var body: some View {
Button(role: .destructive, action: action) {
Label("Delete", systemImage: "trash")
}
}
}
public struct DoneButton: View { public struct DoneButton: View {
let action: () -> Void let action: () -> Void
@@ -15,6 +29,24 @@ public struct DoneButton: View {
} }
} }
public struct EditButton: View {
@Environment(\.editButtonStyle) private var style
let action: () -> Void
public init(action: @escaping () -> Void) {
self.action = action
}
public var body: some View {
Button(action: action) {
Label("Edit", systemImage: "square.and.pencil")
}
.buttonStyle(style)
}
}
public struct InfoButton: View { public struct InfoButton: View {
@Environment(\.infoButtonStyle) private var infoButtonStyle @Environment(\.infoButtonStyle) private var infoButtonStyle

View File

@@ -1,5 +1,10 @@
import SwiftUI import SwiftUI
#warning("Remove button types and just make styles stand-alone")
/// A name space for edit button styles.
public enum EditButtonType { }
/// A name space for info button styles. /// A name space for info button styles.
public enum InfoButtonType { } public enum InfoButtonType { }
@@ -119,6 +124,10 @@ private struct ResetButtonStyleKey: EnvironmentKey {
extension EnvironmentValues { extension EnvironmentValues {
// @Entry var infoButtonStyle: AnyButtonStyle<InfoButtonType> = AnyButtonStyle.default // @Entry var infoButtonStyle: AnyButtonStyle<InfoButtonType> = AnyButtonStyle.default
@Entry var editButtonStyle = MainActor.assumeIsolated {
AnyPrimitiveButtonStyle<EditButtonType>(DefaultInfoButtonStyle(labelStyle: .automatic))
}
var infoButtonStyle: AnyPrimitiveButtonStyle<InfoButtonType> { var infoButtonStyle: AnyPrimitiveButtonStyle<InfoButtonType> {
get { self[InfoButtonStyleKey.self] } get { self[InfoButtonStyleKey.self] }
set { self[InfoButtonStyleKey.self] = newValue } set { self[InfoButtonStyleKey.self] = newValue }
@@ -137,6 +146,16 @@ extension EnvironmentValues {
extension View { extension View {
/// Sets the button style for the ``EditButton`` type.
public func editButtonStyle(_ style: AnyPrimitiveButtonStyle<EditButtonType>) -> some View {
environment(\.editButtonStyle, style)
}
/// Sets the button style for the ``EditButton`` type.
public func editButtonStyle<S: PrimitiveButtonStyle>(_ style: S) -> some View {
editButtonStyle(AnyPrimitiveButtonStyle(style))
}
/// Sets the button style for the ``InfoButton`` type. /// Sets the button style for the ``InfoButton`` type.
public func infoButtonStyle(_ style: AnyPrimitiveButtonStyle<InfoButtonType>) -> some View { public func infoButtonStyle(_ style: AnyPrimitiveButtonStyle<InfoButtonType>) -> some View {
environment(\.infoButtonStyle, style) environment(\.infoButtonStyle, style)