feat: Begins flagged measurement list view.
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user