feat: Adding improvements to pressure estimations feature.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user