import Elementary import ElementaryHTMX import Routes import Styleguide struct HeatingBalancePointForm: HTML, Sendable { let mode: HeatingBalancePoint.Mode let heatLossMode: HeatingBalancePoint.HeatLoss.Mode let response: HeatingBalancePoint.Response? init( mode: HeatingBalancePoint.Mode?, heatLossMode: HeatingBalancePoint.HeatLoss.Mode? = nil, response: HeatingBalancePoint.Response? = nil ) { self.mode = mode ?? .thermal self.heatLossMode = heatLossMode ?? .estimated self.response = response } var content: some HTML { FormHeader(label: "Balance Point - \(mode.label)", svg: .scale) // TODO: Toggle button form( .hx.post(route: .heatingBalancePoint(.index)), .hx.target("#result") ) { div(.class("space-y-6")) { switch mode { case .thermal: ThermalFields(heatLossMode: heatLossMode) case .economic: div {} } div { SubmitButton(label: "Calculate Balance Point") } } } div(.id("result")) { if let response { HeatingBalancePointResponse(response: response) } } } struct ThermalFields: HTML, Sendable { let heatLossMode: HeatingBalancePoint.HeatLoss.Mode var content: some HTML { LabeledContent(label: "System Size (Tons)") { Input(id: "systemSize", placeholder: "System size") .attributes(.type(.number), .min("1"), .step("0.5"), .autofocus, .required) } div { div(.class("mb-4")) { h4(.class("text-lg font-bold")) { "Capacities" } p(.class("text-sm")) { "Entering known capacities gives better results, otherwise capacities will be estimated." } } div(.class("grid grid-cols-1 lg:grid-cols-2 gap-4")) { LabeledContent(label: "Capacity @ 47° (BTU/h)") { Input(id: "capacityAt47", placeholder: "Capacity @ 47° (optional)") .attributes(.type(.number), .min("1"), .step("0.5")) } LabeledContent(label: "Capacity @ 17° (BTU/h)") { Input(id: "capacityAt17", placeholder: "Capacity @ 17° (optional)") .attributes(.type(.number), .min("1"), .step("0.5")) } } } HeatLossFields(mode: heatLossMode) } } struct HeatLossFields: HTML, Sendable { let mode: HeatingBalancePoint.HeatLoss.Mode var content: some HTML { div(.id("heatLossFields")) { div(.class("flex flex-wrap justify-between")) { h4(.class("text-lg font-bold")) { "Heat Loss - \(mode.label)" } Toggle( isOn: mode == .estimated, onLabel: HeatingBalancePoint.HeatLoss.Mode.estimated.label, onAttributes: [ .hx.get(route: .heatingBalancePoint(.heatLossFields(mode: .estimated))), .hx.target("#heatLossFields") ], offLabel: HeatingBalancePoint.HeatLoss.Mode.known.label, offAttributes: [ .hx.get(route: .heatingBalancePoint(.heatLossFields(mode: .known))), .hx.target("#heatLossFields") ] ) .attributes(.class("mb-6")) } switch mode { case .known: knownFields case .estimated: simplifiedFields } } } private var simplifiedFields: some HTML { LabeledContent(label: "Building Size (ft²)") { Input(id: "simplifiedHeatLoss", placeholder: "Square feet") .attributes(.type(.number), .min("1"), .step("0.5"), .required) } } private var knownFields: some HTML { LabeledContent(label: "Heat Loss (BTU/h)") { Input(id: "knownHeatLoss", placeholder: "Heat loss") .attributes(.type(.number), .min("1"), .step("0.5"), .required) } } } } struct HeatingBalancePointResponse: HTML, Sendable { let response: HeatingBalancePoint.Response } private extension HeatingBalancePoint.Mode { var label: String { rawValue.capitalized } } private extension HeatingBalancePoint.HeatLoss.Mode { var label: String { rawValue.capitalized } }