import Elementary import ElementaryHTMX import PsychrometricClient import Routes import Styleguide struct MoldRiskForm: HTML { let response: MoldRisk.Response? var content: some HTML { FormHeader(label: "Mold Risk Calculator", svg: .thermometer) form( // Using index to get the correct path, but really uses the `submit` end-point. .hx.post(route: .moldRisk(.index)), .hx.target("#result") ) { div(.class("space-y-6")) { LabeledContent(label: "Indoor Temperature (°F)") { Input(id: "temperature", placeholder: "Dry bulb temperature") .attributes(.type(.number), .step("0.1"), .min("0.1"), .autofocus, .required) } LabeledContent(label: "Indoor Humdity (%)") { Input(id: "humidity", placeholder: "Relative humidity") .attributes(.type(.number), .step("0.1"), .min("0.1"), .required) } div { SubmitButton(label: "Calculate Mold Risk") } } } div(.id("result")) { if let response { MoldRiskResponse(response: response) } } } } struct MoldRiskResponse: HTML { let response: MoldRisk.Response var content: some HTML { ResultContainer(reset: .moldRisk(.index)) { div( .class(""" p-2 rounded-lg shadow-lg \(response.riskLevel.backgroundColor) border-2 border \(response.riskLevel.borderColor) """) ) { // Risk level and days to mold header. div(.class("\(response.riskLevel.textColor)")) { div(.class("flex flex-wrap mt-2")) { div(.class("w-full sm:w-1/2 flex gap-2")) { SVG(.exclamation, color: "\(response.riskLevel.textColor)") span(.class("text-lg font-extrabold")) { "Risk Level: \(response.riskLevel.rawValue.capitalized)" } } if let daysToMold = response.daysToMold { div(.class("w-full sm:w-1/2 gap-2")) { span(.class("font-semibold")) { "Estimated Days to Mold Growth: " } span { "\(daysToMold) days" } } } } // Recommendations if response.recommendations.count > 0 { div(.class("mt-6 pb-4")) { p(.class("font-semibold mb-4")) { u { "Recommendation\(response.recommendations.count == 1 ? "" : "s"):" } } ul(.class("list-disc mx-10")) { for recommendation in response.recommendations { li { recommendation } } } } } } } // Display psychrometric properties. Properties(properties: response.psychrometricProperties) .attributes(.class("mt-8")) // Disclaimer. Note { """ These calculations are based on typical indoor conditions and common mold species. Actual mold growth can vary based on surface materials, air movement, and other environmental factors. Always address moisture issues promptly and consult professionals for severe cases. """ } } } private struct Properties: HTML, Sendable { let properties: PsychrometricProperties var content: some HTML { div(.class("w-full rounded-lg border")) { h3(.class("flex justify-center text-xl font-semibold mb-6 mt-2")) { "Psychrometric Properties" } PsychrometricPropertiesView(properties: properties) .attributes(.class(""" grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-6 gap-y-2 px-4 pb-4 """)) } } } } private extension MoldRisk.RiskLevel { var backgroundColor: String { switch self { case .low: return "bg-green-200" case .moderate: return "bg-amber-200" case .high: return "bg-orange-200" case .severe: return "bg-red-200" } } var borderColor: String { switch self { case .low: return "border-green-500" case .moderate: return "border-amber-500" case .high: return "border-orange-500" case .severe: return "border-red-500" } } var textColor: String { switch self { case .low: return "text-green-500" case .moderate: return "text-amber-500" case .high: return "text-orange-500" case .severe: return "text-red-500" } } }