import ComposableArchitecture import DependenciesAdditions import EstimatedPressureDependency import FlaggedViews 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 init( destination: Destination.State? = nil, sharedSettings: Shared, estimatedMeasurements: IdentifiedArrayOf = [] ) { 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) case receive(TaskResult) 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 { 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 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 { 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 { @Environment(\.horizontalSizeClass) var horizontalSizeClass @Bindable public var store: StoreOf public init(store: StoreOf) { 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) } .flaggedMessageViewStyle( .automatic(horizontalSizeClass: horizontalSizeClass) ) .flaggedStatusLabelStyle(.textLabel) .flaggedMessageLabelStyle(.font(.caption)) } } #if DEBUG private let sharedSettings = SharedSettings( budgets: .init(equipmentType: .airHandler, fanType: .constantSpeed), equipmentMeasurement: .mock(type: .airHandler), flaggedEquipmentMeasurement: nil ) private let flaggedMeasurements = IdentifiedArrayOf( 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() } ) } } #Preview("Landscape", traits: .landscapeLeft) { NavigationStack { FlaggedMeasurementListView( store: Store( initialState: FlaggedMeasurementsList.State( sharedSettings: Shared(sharedSettings) ) ) { FlaggedMeasurementsList() } ) } } #endif