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 {
Grid(alignment: .leading, horizontalSpacing: 60) {
Grid(alignment: .leading, horizontalSpacing: 80) {
gridRow(for: .airflow)
}
} header: {
HStack {
Spacer()
Text(store.sharedSettings.equipmentMetadata.coolingCapacity.description)
}
} footer: {
HStack {
Spacer()

View File

@@ -355,45 +355,6 @@ fileprivate enum RatingsField: String, Hashable, CaseIterable, Identifiable {
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 {
NavigationStack {
EquipmentSettingsFormView(

View File

@@ -3,35 +3,82 @@ import SharedModels
import Styleguide
import SwiftUI
#warning("Use shared settings, don't display filter pressure drop if current flagged measurement pressure drop is not set.")
@Reducer
public struct EstimationForm {
public init() { }
@ObservableState
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 filterPressureDrop: Double?
public var name: String
public init(
cfmPerTon: Int = 350,
id: SharedPressureEstimationState.FlaggedEstimationContainer.ID? = nil,
existingMeasurement: SharedReader<EquipmentMeasurement?>,
airflowSelection: AirflowSelection = .cfmPerTon,
cfmTextField: Int? = nil,
coolingCapacity: EquipmentMetadata.CoolingCapacity = .default,
filterPressureDrop: Double? = nil,
name: String = ""
) {
self.cfmPerTon = cfmPerTon
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 {
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 {
@@ -68,47 +115,61 @@ public struct EstimationFormView: View {
}
}
Section("Airflow") {
Grid(alignment: .leading, horizontalSpacing: 40) {
GridRow {
HStack {
TextLabel("Capacity")
Spacer()
CoolingCapacityPicker(
selection: $store.coolingCapacity
)
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)
}
}
.gridCellColumns(2)
}
GridRow {
HStack {
TextLabel("CFM / Ton")
Spacer()
TextField(
"CFM / Ton",
value: $store.cfmPerTon,
format: .number,
prompt: Text("CFM")
)
.frame(width: 100)
.numberPad()
GridRow {
HStack {
TextLabel(store.airflowSelection.description)
Spacer()
TextField(
"CFM / Ton",
value: $store.cfmTextField,
format: .number,
prompt: Text("CFM")
)
.frame(width: 100)
.numberPad()
}
.gridCellColumns(2)
}
.gridCellColumns(2)
}
}
}
Section("Filter Pressure Drop") {
HStack {
TextLabel("Pressure Drop")
Spacer()
TextField(
"Filter Drop",
value: $store.filterPressureDrop,
fractionLength: 2,
prompt: Text("Optional")
)
.frame(width: 100)
.decimalPad()
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()
}
}
}
}
@@ -118,9 +179,27 @@ public struct EstimationFormView: View {
}
}
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()) {
store: Store(
initialState: EstimationForm.State(
existingMeasurement: SharedReader(
Shared(EquipmentMeasurement.mock(type: .airHandler))
)
)
) {
EstimationForm()
}
)

View File

@@ -57,8 +57,9 @@ public struct FlaggedMeasurementsList: Sendable {
@CasePathable
public enum View {
case addButtonTapped
case deleteEstimationButtonTapped(id: SharedPressureEstimationState.FlaggedEstimationContainer.ID)
case destination(DestinationAction)
case editButtonTapped(id: SharedPressureEstimationState.FlaggedMeasurementContainer.ID)
case editButtonTapped(id: SharedPressureEstimationState.FlaggedEstimationContainer.ID)
case onAppear
@CasePathable
@@ -107,10 +108,15 @@ public struct FlaggedMeasurementsList: Sendable {
case .addButtonTapped:
state.destination = .estimationForm(.init(
existingMeasurement: state.$sharedSettings.equipmentMeasurement,
coolingCapacity: state.sharedSettings.equipmentMetadata.coolingCapacity
))
return .none
case let .deleteEstimationButtonTapped(id: id):
state.sharedSettings.flaggedEstimations.remove(id: id)
return .none
case let .destination(action):
switch action {
case .cancelButtonTapped:
@@ -237,7 +243,16 @@ public struct FlaggedMeasurementListView: View {
HStack {
Text(measurement.name)
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
private let budgets = BudgetedPercentEnvelope(equipmentType: .airHandler, fanType: .constantSpeed)
private let flaggedMeasurements = IdentifiedArrayOf<SharedPressureEstimationState.FlaggedMeasurementContainer>(
private let flaggedMeasurements = IdentifiedArrayOf<SharedPressureEstimationState.FlaggedEstimationContainer>(
uniqueElements: [
.init(
id: UUID(0),

View File

@@ -9,7 +9,7 @@ public struct SharedPressureEstimationState: Equatable, Sendable {
public var equipmentMeasurement: EquipmentMeasurement?
public var equipmentMetadata: EquipmentMetadata
public var flaggedEquipmentMeasurement: EquipmentMeasurement.FlaggedMeasurement?
public var flaggedEstimations: IdentifiedArrayOf<FlaggedMeasurementContainer>
public var flaggedEstimations: IdentifiedArrayOf<FlaggedEstimationContainer>
public var heatingCapacity: Double?
public var manufacturersIncludedFilterPressureDrop: Double?
@@ -18,7 +18,7 @@ public struct SharedPressureEstimationState: Equatable, Sendable {
equipmentMeasurement: EquipmentMeasurement? = nil,
equipmentMetadata: EquipmentMetadata = .init(),
flaggedEquipmentMeasurement: EquipmentMeasurement.FlaggedMeasurement? = nil,
flaggedEstimations: IdentifiedArrayOf<FlaggedMeasurementContainer> = [],
flaggedEstimations: IdentifiedArrayOf<FlaggedEstimationContainer> = [],
heatingCapacity: Double? = nil,
manufacturersIncludedFilterPressureDrop: Double? = nil
) {
@@ -36,7 +36,8 @@ public struct SharedPressureEstimationState: Equatable, Sendable {
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 var flaggedMeasurement: EquipmentMeasurement.FlaggedMeasurement
public var name: String
@@ -50,6 +51,44 @@ public struct SharedPressureEstimationState: Equatable, Sendable {
self.name = name
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
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 {
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 {
@Environment(\.infoButtonStyle) private var infoButtonStyle

View File

@@ -1,5 +1,10 @@
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.
public enum InfoButtonType { }
@@ -119,6 +124,10 @@ private struct ResetButtonStyleKey: EnvironmentKey {
extension EnvironmentValues {
// @Entry var infoButtonStyle: AnyButtonStyle<InfoButtonType> = AnyButtonStyle.default
@Entry var editButtonStyle = MainActor.assumeIsolated {
AnyPrimitiveButtonStyle<EditButtonType>(DefaultInfoButtonStyle(labelStyle: .automatic))
}
var infoButtonStyle: AnyPrimitiveButtonStyle<InfoButtonType> {
get { self[InfoButtonStyleKey.self] }
set { self[InfoButtonStyleKey.self] = newValue }
@@ -137,6 +146,16 @@ extension EnvironmentValues {
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.
public func infoButtonStyle(_ style: AnyPrimitiveButtonStyle<InfoButtonType>) -> some View {
environment(\.infoButtonStyle, style)