Files
swift-hvac-toolbox/Sources/ViewController/Views/AtticVentilation.swift

183 lines
6.2 KiB
Swift

import Elementary
import ElementaryHTMX
import Routes
import Styleguide
struct AtticVentilationForm: HTML, Sendable {
let response: AtticVentilation.Response?
var content: some HTML {
FormHeader(label: "Attic Ventilation Calculator", svg: .wind)
// Form.
form(
.hx.post(route: .atticVentilation(.index)),
.hx.target("#result")
) {
div(.class("space-y-6")) {
div {
LabeledContent(label: "Pressure Differential: (Pascals - WRT outside)") {
Input(id: "pressureDifferential", placeholder: "Pressure differential")
.attributes(.type(.number), .step("0.1"), .autofocus, .required)
}
p(.class("text-sm text-light mt-2")) { "Positive values indicate higher attic pressure" }
}
div(.class("grid grid-cols-1 md:grid-cols-2 gap-4")) {
div {
p(.class("font-bold mb-6")) { "Outdoor Conditions" }
LabeledContent(label: "Temperature: (°F)") {
Input(id: "outdoorTemperature", placeholder: "Outdoor temperature")
.attributes(.class("mb-4"), .type(.number), .step("0.1"), .required)
}
LabeledContent(label: "Dew Point: (°F)") {
Input(id: "outdoorDewpoint", placeholder: "Outdoor dewpoint")
.attributes(.type(.number), .step("0.1"), .required)
}
}
div {
p(.class("font-bold mb-6")) { "Attic Conditions" }
LabeledContent(label: "Temperature: (°F)") {
Input(id: "atticTemperature", placeholder: "Attic temperature")
.attributes(.class("mb-4"), .type(.number), .step("0.1"), .required)
}
LabeledContent(label: "Dew Point: (°F)") {
Input(id: "atticDewpoint", placeholder: "Attic dewpoint")
.attributes(.type(.number), .step("0.1"), .required)
}
}
}
LabeledContent(label: "Attic Floor Area: (ft²)") {
Input(id: "atticFloorArea", placeholder: "Attic floor area")
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
}
div(.class("grid grid-cols-1 md:grid-cols-2 gap-4")) {
LabeledContent(label: "Existing Intake Area: (ft²)") {
Input(id: "existingIntakeArea", placeholder: "Intake area (optional)")
.attributes(.class("mb-4"), .type(.number), .step("0.1"), .min("0.1"), .required)
}
LabeledContent(label: "Existing Exhaust Area: (ft²)") {
Input(id: "existingExhaustArea", placeholder: "Exhaust area (optional)")
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
}
}
div {
SubmitButton(label: "Calculate Ventilation")
}
}
}
div(.id("result")) {
if let response {
AtticVentilationResponse(response: response)
}
}
}
}
struct AtticVentilationResponse: HTML, Sendable {
let response: AtticVentilation.Response
var content: some HTML {
ResultContainer(reset: .atticVentilation(.index)) {
div(.class("space-y-6")) {
statusView()
requiredVentilationView()
WarningBox(warnings: response.warnings)
Note {
"""
Calculations are based on standard ventilation guidelines and building codes.
Local codes may vary. Consider consulting with a qualified professional for specific
recommendations.
"""
}
}
}
}
private func statusView() -> some HTML {
div(
.class("""
w-full rounded-lg shadow-lg border-2 p-6 \(response.ventilationStatus.borderColor)
\(response.ventilationStatus.textColor) \(response.ventilationStatus.backgroundColor)
""")
) {
div(.class("flex text-2xl mb-6")) {
SVG(.thermometerSun, color: response.ventilationStatus.textColor)
h4(.class("font-extrabold ms-2")) { "Ventilation Status: \(response.ventilationStatus.rawValue.capitalized)" }
}
div(.class("grid justify-items-stretch grid-cols-1 md:grid-cols-3")) {
group("Temperature Differential", "\(double: response.temperatureDifferential, fractionDigits: 1) °F")
group("Dewpoint Differential", "\(double: response.dewpointDifferential, fractionDigits: 1) °F")
group("Pressure Differential", "\(double: response.pressureDifferential, fractionDigits: 1) °F")
}
}
}
private func requiredVentilationView() -> some HTML {
div(
.class("""
w-full rounded-lg border p-6 border-blue-600 text-blue-600 bg-blue-100 dark:bg-blue-300
""")
) {
div(.class("flex text-2xl mb-6")) {
h4(.class("font-extrabold ms-2")) { "Required Ventilation" }
}
div(.class("grid grid-cols-1 md:grid-cols-2 content-center")) {
group("Intake", "\(double: response.requiredVentilationArea.intake, fractionDigits: 1) ft²")
group("Exhaust", "\(double: response.requiredVentilationArea.exhaust, fractionDigits: 1) ft²")
}
if response.recommendations.count > 0 {
div(.class("mt-8")) {
h4(.class("font-bold")) { "Recommendations:" }
ul(.class("list-disc mx-10")) {
for recommendation in response.recommendations {
li { recommendation }
}
}
}
}
}
}
private func group(_ label: String, _ value: String) -> some HTML<HTMLTag.div> {
div(.class("justify-self-center")) {
div(.class("grid grid-cols-1 justify-items-center")) {
p(.class("font-medium")) { label }
h3(.class("text-3xl font-extrabold")) { value }
}
}
}
}
private extension AtticVentilation.VentilationStatus {
var backgroundColor: String {
switch self {
case .adequate: return "bg-green-200"
case .inadequate: return "bg-amber-200"
case .critical: return "bg-red-200"
}
}
var borderColor: String {
switch self {
case .adequate: return "border-green-500"
case .inadequate: return "border-amber-500"
case .critical: return "border-red-500"
}
}
var textColor: String {
switch self {
case .adequate: return "text-green-500"
case .inadequate: return "text-amber-500"
case .critical: return "text-red-500"
}
}
}