feat: Begins hvac system performance

This commit is contained in:
2025-02-27 22:41:37 -05:00
parent dc01477c3e
commit 7cd02971a3
15 changed files with 341 additions and 16 deletions

View File

@@ -78,6 +78,14 @@ extension ViewController: DependencyKey {
return DehumidifierSizeResult(response: response)
}
case let .hvacSystemPerformance(route):
switch route {
case .index:
return request.respond(HVACSystemPerformanceForm())
case .submit:
return HVACSystemPerformanceResult()
}
case let .moldRisk(route):
switch route {
case .index:

View File

@@ -4,6 +4,13 @@ import Routes
import Styleguide
struct DehumidifierSizeForm: HTML {
let response: DehumidifierSize.Response?
init(response: DehumidifierSize.Response? = nil) {
self.response = response
}
var content: some HTML {
FormHeader(label: "Dehumidifier Sizing Calculator", svg: .droplets)
form(
@@ -32,7 +39,11 @@ struct DehumidifierSizeForm: HTML {
}
}
}
div(.id("result")) {}
div(.id("result")) {
if let response {
DehumidifierSizeResult(response: response)
}
}
}
}
@@ -41,16 +52,59 @@ struct DehumidifierSizeResult: HTML {
var content: some HTML {
ResultContainer(reset: .dehumidifierSize(.index)) {
div(.class("""
p-2 rounded-lg shadow-lg bg-blue-500
"""
)) {
p { "Recommended Size: \(response.recommendedSize)" }
div(.class("py-4 block")) {
// Display calculated metrics.
div(.class("""
mb-6 sm:grid sm:grid-cols-1 sm:gap-4 lg:flex items-center lg:justify-between
text-blue-500 dark:text-slate-200
""")) {
div {
span(.class("font-semibold")) { "Base Moisture Load: " }
span(.class("font-light")) { "\(double: response.pintsPerDay) pints/day" }
}
if response.requiredCapacity != response.pintsPerDay {
div {
span(.class("font-semibold")) { "Required Capacity (derated): " }
span(.class("font-light")) { "\(double: response.requiredCapacity) pints/day" }
}
}
}
p(.class("font-semibold mb-4 dark:text-yellow-300")) { "Recommended Size:" }
a(
.target("_blank"), .href(response.recommendedUrl ?? "#"), .rel("noopener noreferrer")
) {
div(.class("""
px-8 py-2 flex items-center justify-between border border-blue-700 rounded-lg shadow-lg group
dark:bg-blue-400 hover:bg-blue-300 hover:dark:bg-blue-600
transition-colors
""")) {
div {
span(.class("font-extrabold text-4xl text-blue-800")) { "\(response.recommendedSize) PPD" }
p(.class("text-sm mt-1")) { "Click to view recommended model →" }
}
div(.class("""
w-12 h-12 rounded-full flex items-center justify-center
bg-blue-500 dark:bg-blue-600
group-hover:bg-blue-600 group-hover:dark:bg-blue-700
transition-colors
""")) {
SVG(.droplets, color: .white)
}
}
}
}
// Display warnings, if applicable
if response.warnings.count > 0 {
div(.class("w-full mt-8 p-4 rounded-lg shadow-lg text-amber-500 bg-amber-200 border border-amber-500")) {
div(.class("""
w-full mt-6 p-4 rounded-lg shadow-lg
text-amber-500
bg-amber-100 dark:bg-amber-200
border border-amber-500
""")) {
span(.class("font-semibold mb-4 border-b")) { "Warning\(response.warnings.count > 1 ? "s:" : ":")" }
ul(.class("list-disc mx-10")) {
for warning in response.warnings {

View File

@@ -0,0 +1,62 @@
import Elementary
import ElementaryHTMX
import Routes
import Styleguide
struct HVACSystemPerformanceForm: HTML, Sendable {
var content: some HTML {
FormHeader(label: "HVAC System Performance", svg: .thermometerSun)
form(
// Using index to get the correct path, but really uses the `submit` end-point.
.hx.post(route: .hvacSystemPerformance(.index)),
.hx.target("#result")
) {
div(.class("space-y-6")) {
div(.class("grid grid-cols-1 md:grid-cols-3 gap-4")) {
LabeledContent(label: "System Size (Tons)") {
Input(id: "systemSize", placeholder: "System size")
.attributes(.type(.number), .min("1"), .step("0.5"), .autofocus, .required)
}
LabeledContent(label: "Airflow (CFM)") {
Input(id: "airflow", placeholder: "Airflow")
.attributes(.type(.number), .min("1"), .step("0.5"), .required)
}
LabeledContent(label: "Altitude (ft.)") {
Input(id: "altitude", placeholder: "Altitude (Optional)")
.attributes(.type(.number), .min("1"), .step("0.5"))
}
}
div(.class("grid grid-cols-1 md:grid-cols-2 gap-4")) {
div(.class("space-y-4")) {
h3(.class("text-lg font-medium")) { "Return Air" }
LabeledContent(label: "Dry Bulb (°F)") {
Input(id: "returnAirTemperature", placeholder: "Return temperature")
}
LabeledContent(label: "Indoor Humdity (%)") {
Input(id: "returnAirHumidity", placeholder: "Return humidity")
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
}
}
div(.class("space-y-4")) {
h3(.class("text-lg font-medium")) { "Supply Air" }
LabeledContent(label: "Dry Bulb (°F)") {
Input(id: "supplyAirTemperature", placeholder: "Supply temperature")
}
LabeledContent(label: "Indoor Humdity (%)") {
Input(id: "supplyAirHumidity", placeholder: "Supply humidity")
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
}
}
}
div {
SubmitButton(label: "Calculate Performance")
}
}
}
div(.id("result")) {}
}
}
struct HVACSystemPerformanceResult: HTML, Sendable {}

View File

@@ -77,6 +77,14 @@ private struct Header: HTML {
"Dehumidifier-Sizing"
}
}
li {
a(
.class("hover:border-b \(border: .yellow)"),
.hx.get(route: .hvacSystemPerformance(.index)), .hx.target("#content"), .hx.pushURL(true)
) {
"HVAC-System-Performance"
}
}
}
}
}
@@ -88,7 +96,7 @@ private struct PageContent<Body: HTML>: HTML where Body: Sendable {
var content: some HTML {
div(.class("mx-5 lg:mx-20")) {
div(.class("rounded-xl shadow-lg bg-white dark:bg-slate-700 p-8")) {
div(.class("rounded-xl shadow-lg bg-white dark:bg-slate-700 p-8 text-gray-600 dark:text-slate-200")) {
div(.id("content")) {
body()
}

View File

@@ -18,12 +18,12 @@ struct MoldRiskForm: HTML {
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)
.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"))
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
}
div {