feat: Begins flagged measurement list view.
This commit is contained in:
@@ -80,66 +80,66 @@ extension EstimatedPressureDependency {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func estimatedPressure(
|
public func estimatedPressure(
|
||||||
for equipmentMeasurement: EquipmentMeasurement,
|
equipmentMeasurement: EquipmentMeasurement,
|
||||||
at upgradedAirflow: Double
|
airflow updatedAirflow: Double
|
||||||
) async throws -> EquipmentMeasurement {
|
) async throws -> EquipmentMeasurement {
|
||||||
switch equipmentMeasurement {
|
switch equipmentMeasurement {
|
||||||
case let .airHandler(airHandler):
|
case let .airHandler(airHandler):
|
||||||
guard let airflow = airHandler.airflow else {
|
guard let existingAirflow = airHandler.airflow else {
|
||||||
throw InvalidAirflow()
|
throw InvalidAirflow()
|
||||||
}
|
}
|
||||||
return try await .airHandler(
|
return try await .airHandler(
|
||||||
.init(
|
.init(
|
||||||
airflow: upgradedAirflow,
|
airflow: updatedAirflow,
|
||||||
returnPlenumPressure: self.estimatedPressure(
|
returnPlenumPressure: self.estimatedPressure(
|
||||||
existingPressure: airHandler.$returnPlenumPressure,
|
existingPressure: airHandler.$returnPlenumPressure,
|
||||||
existingAirflow: airflow,
|
existingAirflow: existingAirflow,
|
||||||
targetAirflow: upgradedAirflow
|
targetAirflow: updatedAirflow
|
||||||
),
|
),
|
||||||
postFilterPressure: self.estimatedPressure(
|
postFilterPressure: self.estimatedPressure(
|
||||||
existingPressure: airHandler.$postFilterPressure,
|
existingPressure: airHandler.$postFilterPressure,
|
||||||
existingAirflow: airflow,
|
existingAirflow: existingAirflow,
|
||||||
targetAirflow: upgradedAirflow
|
targetAirflow: updatedAirflow
|
||||||
),
|
),
|
||||||
postCoilPressure: self.estimatedPressure(
|
postCoilPressure: self.estimatedPressure(
|
||||||
existingPressure: airHandler.$postCoilPressure,
|
existingPressure: airHandler.$postCoilPressure,
|
||||||
existingAirflow: airflow,
|
existingAirflow: existingAirflow,
|
||||||
targetAirflow: upgradedAirflow
|
targetAirflow: updatedAirflow
|
||||||
),
|
),
|
||||||
supplyPlenumPressure: self.estimatedPressure(
|
supplyPlenumPressure: self.estimatedPressure(
|
||||||
existingPressure: airHandler.$supplyPlenumPressure,
|
existingPressure: airHandler.$supplyPlenumPressure,
|
||||||
existingAirflow: airflow,
|
existingAirflow: existingAirflow,
|
||||||
targetAirflow: upgradedAirflow
|
targetAirflow: updatedAirflow
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
case let .furnaceAndCoil(furnaceAndCoil):
|
case let .furnaceAndCoil(furnaceAndCoil):
|
||||||
guard let airflow = furnaceAndCoil.airflow else {
|
guard let existingAirflow = furnaceAndCoil.airflow else {
|
||||||
throw InvalidAirflow()
|
throw InvalidAirflow()
|
||||||
}
|
}
|
||||||
return try await .furnaceAndCoil(
|
return try await .furnaceAndCoil(
|
||||||
.init(
|
.init(
|
||||||
airflow: upgradedAirflow,
|
airflow: updatedAirflow,
|
||||||
returnPlenumPressure: self.estimatedPressure(
|
returnPlenumPressure: self.estimatedPressure(
|
||||||
existingPressure: furnaceAndCoil.$returnPlenumPressure,
|
existingPressure: furnaceAndCoil.$returnPlenumPressure,
|
||||||
existingAirflow: airflow,
|
existingAirflow: existingAirflow,
|
||||||
targetAirflow: upgradedAirflow
|
targetAirflow: updatedAirflow
|
||||||
),
|
),
|
||||||
postFilterPressure: self.estimatedPressure(
|
postFilterPressure: self.estimatedPressure(
|
||||||
existingPressure: furnaceAndCoil.$postFilterPressure,
|
existingPressure: furnaceAndCoil.$postFilterPressure,
|
||||||
existingAirflow: airflow,
|
existingAirflow: existingAirflow,
|
||||||
targetAirflow: upgradedAirflow
|
targetAirflow: updatedAirflow
|
||||||
),
|
),
|
||||||
preCoilPressure: self.estimatedPressure(
|
preCoilPressure: self.estimatedPressure(
|
||||||
existingPressure: furnaceAndCoil.$preCoilPressure,
|
existingPressure: furnaceAndCoil.$preCoilPressure,
|
||||||
existingAirflow: airflow,
|
existingAirflow: existingAirflow,
|
||||||
targetAirflow: upgradedAirflow
|
targetAirflow: updatedAirflow
|
||||||
),
|
),
|
||||||
supplyPlenumPressure: self.estimatedPressure(
|
supplyPlenumPressure: self.estimatedPressure(
|
||||||
existingPressure: furnaceAndCoil.$supplyPlenumPressure,
|
existingPressure: furnaceAndCoil.$supplyPlenumPressure,
|
||||||
existingAirflow: airflow,
|
existingAirflow: existingAirflow,
|
||||||
targetAirflow: upgradedAirflow
|
targetAirflow: updatedAirflow
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -148,13 +148,13 @@ extension EstimatedPressureDependency {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func estimatedPressure(
|
public func estimatedPressure(
|
||||||
for equipmentMeasurement: EquipmentMeasurement,
|
equipmentMeasurement: EquipmentMeasurement,
|
||||||
at upgradedAirflow: Double,
|
airflow updatedAirflow: Double,
|
||||||
with filterPressureDrop: Positive<Double>
|
filterPressureDrop: Positive<Double>
|
||||||
) async throws -> EquipmentMeasurement {
|
) async throws -> EquipmentMeasurement {
|
||||||
let estimate = try await estimatedPressure(
|
let estimate = try await estimatedPressure(
|
||||||
for: equipmentMeasurement,
|
equipmentMeasurement: equipmentMeasurement,
|
||||||
at: upgradedAirflow
|
airflow: updatedAirflow
|
||||||
)
|
)
|
||||||
|
|
||||||
switch estimate {
|
switch estimate {
|
||||||
@@ -167,8 +167,24 @@ extension EstimatedPressureDependency {
|
|||||||
furnaceAndCoil.postFilterPressure = furnaceAndCoil.returnPlenumPressure + filterPressureDrop.positiveValue
|
furnaceAndCoil.postFilterPressure = furnaceAndCoil.returnPlenumPressure + filterPressureDrop.positiveValue
|
||||||
return .furnaceAndCoil(furnaceAndCoil)
|
return .furnaceAndCoil(furnaceAndCoil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func estimatedPressure(
|
||||||
|
equipmentMeasurement: EquipmentMeasurement,
|
||||||
|
airflow updatedAirflow: Double,
|
||||||
|
filterPressureDrop: Positive<Double>?
|
||||||
|
) async throws -> EquipmentMeasurement {
|
||||||
|
guard let filterPressureDrop else {
|
||||||
|
return try await estimatedPressure(
|
||||||
|
equipmentMeasurement: equipmentMeasurement,
|
||||||
|
airflow: updatedAirflow
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return try await estimatedPressure(
|
||||||
|
equipmentMeasurement: equipmentMeasurement,
|
||||||
|
airflow: updatedAirflow,
|
||||||
|
filterPressureDrop: filterPressureDrop
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import SharedModels
|
|||||||
import Styleguide
|
import Styleguide
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
@Reducer
|
@Reducer
|
||||||
public struct EquipmentMeasurementForm {
|
public struct EquipmentMeasurementForm {
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import SharedModels
|
|||||||
import Styleguide
|
import Styleguide
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
@Reducer
|
@Reducer
|
||||||
public struct EquipmentSettingsForm {
|
public struct EquipmentSettingsForm {
|
||||||
|
|
||||||
|
|||||||
126
Sources/PressureEstimationsFeature/EstimationsForm.swift
Normal file
126
Sources/PressureEstimationsFeature/EstimationsForm.swift
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import ComposableArchitecture
|
||||||
|
import SharedModels
|
||||||
|
import Styleguide
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@Reducer
|
||||||
|
public struct EstimationForm {
|
||||||
|
public init() { }
|
||||||
|
|
||||||
|
@ObservableState
|
||||||
|
public struct State: Equatable {
|
||||||
|
public var cfmPerTon: Int
|
||||||
|
public var coolingCapacity: CoolingCapacity
|
||||||
|
public var filterPressureDrop: Double?
|
||||||
|
public var name: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
cfmPerTon: Int = 350,
|
||||||
|
coolingCapacity: CoolingCapacity = .default,
|
||||||
|
filterPressureDrop: Double? = nil,
|
||||||
|
name: String = ""
|
||||||
|
) {
|
||||||
|
self.cfmPerTon = cfmPerTon
|
||||||
|
self.filterPressureDrop = filterPressureDrop
|
||||||
|
self.coolingCapacity = coolingCapacity
|
||||||
|
self.name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
public var airflow: Double {
|
||||||
|
Double(cfmPerTon) * coolingCapacity.rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isValid: Bool { !name.isEmpty }
|
||||||
|
}
|
||||||
|
|
||||||
|
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") {
|
||||||
|
Grid(alignment: .leading, horizontalSpacing: 40) {
|
||||||
|
GridRow {
|
||||||
|
HStack {
|
||||||
|
TextLabel("Capacity")
|
||||||
|
Spacer()
|
||||||
|
CoolingCapacityPicker(
|
||||||
|
selection: $store.coolingCapacity
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.gridCellColumns(2)
|
||||||
|
}
|
||||||
|
GridRow {
|
||||||
|
HStack {
|
||||||
|
TextLabel("CFM / Ton")
|
||||||
|
Spacer()
|
||||||
|
TextField(
|
||||||
|
"CFM / Ton",
|
||||||
|
value: $store.cfmPerTon,
|
||||||
|
format: .number,
|
||||||
|
prompt: Text("CFM")
|
||||||
|
)
|
||||||
|
.frame(width: 100)
|
||||||
|
.numberPad()
|
||||||
|
}
|
||||||
|
.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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.labelsHidden()
|
||||||
|
.textLabelStyle(.boldSecondary)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
EstimationFormView(
|
||||||
|
store: Store(initialState: EstimationForm.State()) {
|
||||||
|
EstimationForm()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
307
Sources/PressureEstimationsFeature/FlaggedMeasurementsList.swift
Normal file
307
Sources/PressureEstimationsFeature/FlaggedMeasurementsList.swift
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
import ComposableArchitecture
|
||||||
|
import DependenciesAdditions
|
||||||
|
import EstimatedPressureDependency
|
||||||
|
import SharedModels
|
||||||
|
import Styleguide
|
||||||
|
import SwiftUI
|
||||||
|
import TCAExtras
|
||||||
|
|
||||||
|
|
||||||
|
@Reducer
|
||||||
|
public struct FlaggedMeasurementsList {
|
||||||
|
|
||||||
|
@Reducer(state: .equatable)
|
||||||
|
public enum Destination {
|
||||||
|
case estimationForm(EstimationForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObservableState
|
||||||
|
public struct State: Equatable {
|
||||||
|
|
||||||
|
@Presents public var destination: Destination.State?
|
||||||
|
@Shared var sharedSettings: SharedSettings
|
||||||
|
public var estimatedMeasurements: IdentifiedArrayOf<FlaggedMeasurementContainer>
|
||||||
|
|
||||||
|
init(
|
||||||
|
destination: Destination.State? = nil,
|
||||||
|
sharedSettings: Shared<SharedSettings>,
|
||||||
|
estimatedMeasurements: IdentifiedArrayOf<FlaggedMeasurementContainer> = []
|
||||||
|
) {
|
||||||
|
self.destination = destination
|
||||||
|
self._sharedSettings = sharedSettings
|
||||||
|
self.estimatedMeasurements = estimatedMeasurements
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct FlaggedMeasurementContainer: Equatable, Identifiable {
|
||||||
|
public let id: UUID
|
||||||
|
public var flaggedMeasurement: FlaggedEquipmentMeasurement
|
||||||
|
public var name: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: UUID,
|
||||||
|
name: String,
|
||||||
|
flaggedMeasurement: FlaggedEquipmentMeasurement
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.flaggedMeasurement = flaggedMeasurement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Action: ViewAction, ReceiveAction {
|
||||||
|
|
||||||
|
case destination(PresentationAction<Destination.Action>)
|
||||||
|
case receive(TaskResult<ReceiveAction>)
|
||||||
|
case view(View)
|
||||||
|
|
||||||
|
@CasePathable
|
||||||
|
public enum ReceiveAction {
|
||||||
|
case existingFlaggedMeasurement(FlaggedEquipmentMeasurement)
|
||||||
|
case estimatedFlaggedMeasurement(name: String, measurement: FlaggedEquipmentMeasurement)
|
||||||
|
}
|
||||||
|
|
||||||
|
@CasePathable
|
||||||
|
public enum View {
|
||||||
|
case addButtonTapped
|
||||||
|
case destination(DestinationAction)
|
||||||
|
case editButtonTapped(id: State.FlaggedMeasurementContainer.ID)
|
||||||
|
case onAppear
|
||||||
|
|
||||||
|
@CasePathable
|
||||||
|
public enum DestinationAction {
|
||||||
|
case cancelButtonTapped
|
||||||
|
case doneButtonTapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dependency(\.estimatedPressuresClient) var estimatedPressuresClient
|
||||||
|
@Dependency(\.logger["\(Self.self)"]) var logger
|
||||||
|
@Dependency(\.uuid) var uuid
|
||||||
|
|
||||||
|
public var body: some Reducer<State, Action> {
|
||||||
|
ReceiveReducer { state, action in
|
||||||
|
switch action {
|
||||||
|
case let .existingFlaggedMeasurement(measurement):
|
||||||
|
state.sharedSettings.flaggedEquipmentMeasurement = measurement
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case let .estimatedFlaggedMeasurement(name: name, measurement: measurement):
|
||||||
|
state.estimatedMeasurements.append(
|
||||||
|
.init(
|
||||||
|
id: uuid(),
|
||||||
|
name: name,
|
||||||
|
flaggedMeasurement: measurement
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onFailure(.log(logger: logger))
|
||||||
|
|
||||||
|
Reduce<State, Action> { state, action in
|
||||||
|
switch action {
|
||||||
|
|
||||||
|
case .destination:
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .receive:
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case let .view(action):
|
||||||
|
switch action {
|
||||||
|
|
||||||
|
case .addButtonTapped:
|
||||||
|
state.destination = .estimationForm(.init(
|
||||||
|
coolingCapacity: state.sharedSettings.coolingCapacity
|
||||||
|
))
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case let .destination(action):
|
||||||
|
switch action {
|
||||||
|
case .cancelButtonTapped:
|
||||||
|
state.destination = nil
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .doneButtonTapped:
|
||||||
|
guard case let .estimationForm(form) = state.destination else {
|
||||||
|
return .fail(
|
||||||
|
"""
|
||||||
|
Received estimation form done button tapped action, but form
|
||||||
|
was not presented.
|
||||||
|
|
||||||
|
This is considered an application logic error.
|
||||||
|
""",
|
||||||
|
logger: logger
|
||||||
|
)
|
||||||
|
}
|
||||||
|
state.destination = nil
|
||||||
|
return handleEstimationForm(form: form, state: state)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .editButtonTapped(id: _):
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .onAppear:
|
||||||
|
guard let equipmentMeasurement = state.sharedSettings.equipmentMeasurement,
|
||||||
|
let budgets = state.sharedSettings.budgets
|
||||||
|
else {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
state.sharedSettings.flaggedEquipmentMeasurement = .init(
|
||||||
|
budgets: budgets,
|
||||||
|
measurement: equipmentMeasurement,
|
||||||
|
ratedPressures: state.sharedSettings.ratedStaticPressures,
|
||||||
|
tons: state.sharedSettings.coolingCapacity
|
||||||
|
)
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ifLet(\.$destination, action: \.destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleEstimationForm(form: EstimationForm.State, state: State) -> Effect<Action> {
|
||||||
|
guard let equipmentMeasurement = state.sharedSettings.equipmentMeasurement,
|
||||||
|
let budgets = state.sharedSettings.budgets
|
||||||
|
else {
|
||||||
|
return .fail(
|
||||||
|
"""
|
||||||
|
Received estimation form done button tapped action, original
|
||||||
|
equipment measurement or budgets are not set on the shared state.
|
||||||
|
|
||||||
|
This is considered an application logic error.
|
||||||
|
""",
|
||||||
|
logger: logger
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .receive(action: \.receive) { [ratedStaticPressures = state.sharedSettings.ratedStaticPressures] in
|
||||||
|
|
||||||
|
let filterPressureDrop = form.filterPressureDrop != nil
|
||||||
|
? Positive(wrappedValue: form.filterPressureDrop!)
|
||||||
|
: nil
|
||||||
|
|
||||||
|
let measurement = try await estimatedPressuresClient.estimatedPressure(
|
||||||
|
equipmentMeasurement: equipmentMeasurement,
|
||||||
|
airflow: form.airflow,
|
||||||
|
filterPressureDrop: filterPressureDrop
|
||||||
|
)
|
||||||
|
|
||||||
|
let flaggedMeasurement = FlaggedEquipmentMeasurement(
|
||||||
|
budgets: budgets,
|
||||||
|
measurement: measurement,
|
||||||
|
ratedPressures: ratedStaticPressures,
|
||||||
|
tons: form.coolingCapacity
|
||||||
|
)
|
||||||
|
|
||||||
|
return .estimatedFlaggedMeasurement(name: form.name, measurement: flaggedMeasurement)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewAction(for: FlaggedMeasurementsList.self)
|
||||||
|
public struct FlaggedMeasurementListView: View {
|
||||||
|
@Bindable public var store: StoreOf<FlaggedMeasurementsList>
|
||||||
|
|
||||||
|
public init(store: StoreOf<FlaggedMeasurementsList>) {
|
||||||
|
self.store = store
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
List {
|
||||||
|
if let existingMeasurement = store.sharedSettings.flaggedEquipmentMeasurement {
|
||||||
|
Section {
|
||||||
|
FlaggedEquipmentMeasurementView(existingMeasurement)
|
||||||
|
} header: {
|
||||||
|
HStack {
|
||||||
|
Text("Existing Measurements")
|
||||||
|
Spacer()
|
||||||
|
// Button("Edit") { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ForEach(store.estimatedMeasurements) { measurement in
|
||||||
|
Section {
|
||||||
|
FlaggedEquipmentMeasurementView(measurement.flaggedMeasurement)
|
||||||
|
} header: {
|
||||||
|
HStack {
|
||||||
|
Text(measurement.name)
|
||||||
|
Spacer()
|
||||||
|
Button("Edit") { send(.editButtonTapped(id: measurement.id)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
Button { send(.addButtonTapped) } label: {
|
||||||
|
Label("Add", systemImage: "plus")
|
||||||
|
}
|
||||||
|
.sheet(
|
||||||
|
item: $store.scope(
|
||||||
|
state: \.destination?.estimationForm,
|
||||||
|
action: \.destination.estimationForm
|
||||||
|
)
|
||||||
|
) { store in
|
||||||
|
NavigationStack {
|
||||||
|
EstimationFormView(store: store)
|
||||||
|
.navigationTitle("Estimation")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button{ send(.destination(.cancelButtonTapped)) } label: {
|
||||||
|
Text("Cancel")
|
||||||
|
.foregroundStyle(Color.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
|
DoneButton { send(.destination(.doneButtonTapped)) }
|
||||||
|
.disabled(!store.isValid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear { send(.onAppear) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private let sharedSettings = SharedSettings(
|
||||||
|
budgets: .init(equipmentType: .airHandler, fanType: .constantSpeed),
|
||||||
|
equipmentMeasurement: .mock(type: .airHandler),
|
||||||
|
flaggedEquipmentMeasurement: nil
|
||||||
|
)
|
||||||
|
|
||||||
|
private let flaggedMeasurements = IdentifiedArrayOf<FlaggedMeasurementsList.State.FlaggedMeasurementContainer>(
|
||||||
|
uniqueElements: [
|
||||||
|
.init(
|
||||||
|
id: UUID(0),
|
||||||
|
name: "Existing",
|
||||||
|
flaggedMeasurement: .init(
|
||||||
|
budgets: sharedSettings.budgets!,
|
||||||
|
measurement: sharedSettings.equipmentMeasurement!,
|
||||||
|
ratedPressures: sharedSettings.ratedStaticPressures,
|
||||||
|
tons: sharedSettings.coolingCapacity
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
NavigationStack {
|
||||||
|
FlaggedMeasurementListView(
|
||||||
|
store: Store(
|
||||||
|
initialState: FlaggedMeasurementsList.State(
|
||||||
|
sharedSettings: Shared(sharedSettings)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
FlaggedMeasurementsList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
83
Sources/PressureEstimationsFeature/PressureEstimations.swift
Normal file
83
Sources/PressureEstimationsFeature/PressureEstimations.swift
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import ComposableArchitecture
|
||||||
|
import SharedModels
|
||||||
|
import Styleguide
|
||||||
|
import SwiftUI
|
||||||
|
import TCAExtras
|
||||||
|
|
||||||
|
@Reducer
|
||||||
|
public struct PressureEstimationsFeature {
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
|
||||||
|
@Reducer(state: .equatable)
|
||||||
|
public enum Destination {
|
||||||
|
case equipmentMeasurements(EquipmentMeasurementForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObservableState
|
||||||
|
public struct State: Equatable {
|
||||||
|
@Presents public var destination: Destination.State?
|
||||||
|
public var equipmentSettings: EquipmentSettingsForm.State
|
||||||
|
public var equipmentMeasurements: EquipmentMeasurementForm.State?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
destination: Destination.State? = nil,
|
||||||
|
equipmentSettings: EquipmentSettingsForm.State = .init(),
|
||||||
|
equipmentMeasurements: EquipmentMeasurementForm.State? = nil
|
||||||
|
) {
|
||||||
|
self.destination = destination
|
||||||
|
self.equipmentSettings = equipmentSettings
|
||||||
|
self.equipmentMeasurements = equipmentMeasurements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Action: ViewAction {
|
||||||
|
case destination(PresentationAction<Destination.Action>)
|
||||||
|
case equipmentSettings(EquipmentSettingsForm.Action)
|
||||||
|
case view(View)
|
||||||
|
|
||||||
|
@CasePathable
|
||||||
|
public enum View {
|
||||||
|
case nextButtonTapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some Reducer<State, Action> {
|
||||||
|
Scope(state: \.equipmentSettings, action: \.equipmentSettings) {
|
||||||
|
EquipmentSettingsForm()
|
||||||
|
}
|
||||||
|
Reduce<State, Action> { state, action in
|
||||||
|
switch action {
|
||||||
|
case .destination:
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .equipmentSettings:
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case let .view(action):
|
||||||
|
switch action {
|
||||||
|
case .nextButtonTapped:
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ifLet(\.$destination, action: \.destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewAction(for: PressureEstimationsFeature.self)
|
||||||
|
public struct PressureEstimationsView: View {
|
||||||
|
|
||||||
|
@Bindable public var store: StoreOf<PressureEstimationsFeature>
|
||||||
|
|
||||||
|
public init(store: StoreOf<PressureEstimationsFeature>) {
|
||||||
|
self.store = store
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
EquipmentSettingsFormView(
|
||||||
|
store: store.scope(state: \.equipmentSettings, action: \.equipmentSettings)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Sources/PressureEstimationsFeature/SharedSettings.swift
Normal file
37
Sources/PressureEstimationsFeature/SharedSettings.swift
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import ComposableArchitecture
|
||||||
|
import SharedModels
|
||||||
|
|
||||||
|
|
||||||
|
struct SharedSettings: Equatable {
|
||||||
|
var budgets: BudgetedPercentEnvelope?
|
||||||
|
var coolingCapacity: CoolingCapacity
|
||||||
|
var equipmentMeasurement: EquipmentMeasurement?
|
||||||
|
var fanType: FanType
|
||||||
|
var flaggedEquipmentMeasurement: FlaggedEquipmentMeasurement?
|
||||||
|
var heatingCapacity: Double?
|
||||||
|
var ratedStaticPressures: RatedStaticPressures
|
||||||
|
|
||||||
|
init(
|
||||||
|
budgets: BudgetedPercentEnvelope? = nil,
|
||||||
|
coolingCapacity: CoolingCapacity = .default,
|
||||||
|
equipmentMeasurement: EquipmentMeasurement? = nil,
|
||||||
|
fanType: FanType = .constantSpeed,
|
||||||
|
flaggedEquipmentMeasurement: FlaggedEquipmentMeasurement? = nil,
|
||||||
|
heatingCapacity: Double? = nil,
|
||||||
|
ratedStaticPressures: RatedStaticPressures = .init()
|
||||||
|
) {
|
||||||
|
self.budgets = budgets
|
||||||
|
self.coolingCapacity = coolingCapacity
|
||||||
|
self.equipmentMeasurement = equipmentMeasurement
|
||||||
|
self.fanType = fanType
|
||||||
|
self.flaggedEquipmentMeasurement = flaggedEquipmentMeasurement
|
||||||
|
self.heatingCapacity = heatingCapacity
|
||||||
|
self.ratedStaticPressures = ratedStaticPressures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PersistenceReaderKey where Self == InMemoryKey<SharedSettings> {
|
||||||
|
static var sharedSettings: Self {
|
||||||
|
.inMemory("sharedSettings")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import ComposableArchitecture
|
|
||||||
import SharedModels
|
|
||||||
import SwiftUI
|
|
||||||
import TCAExtras
|
|
||||||
19
Sources/Styleguide/Pickers/CoolingCapacityPicker.swift
Normal file
19
Sources/Styleguide/Pickers/CoolingCapacityPicker.swift
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import SharedModels
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct CoolingCapacityPicker: View {
|
||||||
|
@Binding var selection: CoolingCapacity
|
||||||
|
|
||||||
|
public init(selection: Binding<CoolingCapacity>) {
|
||||||
|
self._selection = selection
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
Picker("Cooling Capacity", selection: $selection) {
|
||||||
|
ForEach(CoolingCapacity.allCases) {
|
||||||
|
Text($0.description)
|
||||||
|
.tag($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -172,7 +172,9 @@ extension Flagged {
|
|||||||
.foregroundStyle(flagColor)
|
.foregroundStyle(flagColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var messageView: some View { flaggedMessageView(flagged: self) }
|
public var messageView: some View {
|
||||||
|
flaggedMessageView(flagged: self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|||||||
Reference in New Issue
Block a user