214 lines
7.3 KiB
Swift
214 lines
7.3 KiB
Swift
import Elementary
|
|
import ElementaryHTMX
|
|
import PsychrometricClient
|
|
import Routes
|
|
import Styleguide
|
|
|
|
struct HVACSystemPerformanceForm: HTML, Sendable {
|
|
|
|
let response: HVACSystemPerformance.Response?
|
|
|
|
init(response: HVACSystemPerformance.Response? = nil) {
|
|
self.response = response
|
|
}
|
|
|
|
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: "Project altitude (Optional)")
|
|
.attributes(.type(.number), .min("1"), .step("0.5"))
|
|
}
|
|
}
|
|
|
|
div(.class("grid grid-cols-1 md:grid-cols-2 gap-8")) {
|
|
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")) {
|
|
if let response {
|
|
HVACSystemPerformanceResult(response: response)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct HVACSystemPerformanceResult: HTML, Sendable {
|
|
let response: HVACSystemPerformance.Response
|
|
|
|
var content: some HTML {
|
|
ResultContainer(reset: .hvacSystemPerformance(.index)) {
|
|
// ResultContainer {
|
|
// Capacities
|
|
div(.class("grid grid-cols-1 lg:grid-cols-3 gap-4")) {
|
|
CapacityContainer(title: "Total Capacity", capacity: response.capacity.total, shr: nil)
|
|
.attributes(.class("text-blue-600 bg-blue-100 border border-blue-600 p-6"))
|
|
|
|
CapacityContainer(title: "Sensible Capacity", capacity: response.capacity.sensible, shr: response.capacity.shr)
|
|
.attributes(.class("text-green-600 bg-green-100 border border-green-600 p-6"))
|
|
|
|
CapacityContainer(title: "Latent Capacity", capacity: response.capacity.latent, shr: nil)
|
|
.attributes(.class("text-purple-600 bg-purple-100 border border-purple-600 p-6"))
|
|
}
|
|
|
|
// System Metrics
|
|
SystemMetricsView(metrics: response.systemMetrics, shr: response.capacity.shr)
|
|
.attributes(.class("mt-8 border rounded-xl shadow-lg"))
|
|
|
|
// Psychrometric properties.
|
|
div(.class("grid grid-cols-1 md:grid-cols-2 gap-6 mt-8")) {
|
|
PsychrometricPropertiesGrid(title: "Return Air Properties", properties: response.returnAirProperties)
|
|
PsychrometricPropertiesGrid(title: "Supply Air Properties", properties: response.supplyAirProperties)
|
|
}
|
|
}
|
|
// header: {
|
|
// p { "Fix me." }
|
|
// }
|
|
}
|
|
|
|
private struct CapacityContainer: HTML, Sendable {
|
|
let title: String
|
|
let capacity: Double
|
|
let shr: Double?
|
|
|
|
var tons: Double { capacity / 12000 }
|
|
|
|
var content: some HTML<HTMLTag.div> {
|
|
div(.class("rounded-xl")) {
|
|
h4(.class("text-lg font-semibold mb-2")) { title }
|
|
div(.class("text-3xl font-bold")) {
|
|
"\(double: tons, fractionDigits: 1)"
|
|
span(.class("text-xl ps-2")) { "Tons" }
|
|
}
|
|
div(.class("text-sm mt-1")) {
|
|
"\(double: capacity, fractionDigits: 1) BTU/h"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct SystemMetricsView: HTML, Sendable {
|
|
let metrics: HVACSystemPerformance.SystemMetrics
|
|
let shr: Double
|
|
|
|
var content: some HTML<HTMLTag.div> {
|
|
div {
|
|
h4(.class("""
|
|
text-lg font-semibold flex justify-center py-2 mb-4 text-blue-600 dark:text-slate-300
|
|
""")) { "System Performance Metrics" }
|
|
|
|
// grid grid-cols-1 md:grid-cols-2 gap-6
|
|
div(.class("flex justify-between items-center p-4")) {
|
|
div(.class("space-y-3")) {
|
|
LabeledMetric(label: "Airflow per Ton", value: metrics.cfmPerTon, units: "CFM/ton")
|
|
.attributes(.class("mb-8"))
|
|
|
|
div {
|
|
LabeledMetric(label: "Temperature Split", value: metrics.actualTemperatureSplit, units: "°F")
|
|
p(.class("text-xs")) {
|
|
"Target: \(double: metrics.targetTemperatureSplit) °F"
|
|
}
|
|
}
|
|
}
|
|
div(.class("space-y-3")) {
|
|
LabeledMetric(label: "Moisture Removal", value: metrics.condensationRateGallonsPerHour, units: "gal/h")
|
|
.attributes(.class("mb-8"))
|
|
LabeledMetric(label: "Sensible Heat Ratio", value: shr, units: "%")
|
|
}
|
|
}
|
|
|
|
if shr < 0.7 {
|
|
div(.class("mx-8 mb-4 mt-8 p-4 rounded-xl shadow-lg border border-amber-500 bg-amber-200 text-amber-500")) {
|
|
h4(.class("text-lg font-semibold")) { "Warning:" }
|
|
p(.class("text-sm")) {
|
|
"Low sensible heat ratio may indicate excessive dehumidification or low airflow."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct LabeledMetric: HTML, Sendable {
|
|
let label: String
|
|
let value: Double
|
|
let units: String
|
|
let fractionDigits: Int
|
|
|
|
init(
|
|
label: String,
|
|
value: Double,
|
|
units: String,
|
|
fractionDigits: Int = 1
|
|
) {
|
|
self.label = label
|
|
self.value = value
|
|
self.units = units
|
|
self.fractionDigits = fractionDigits
|
|
}
|
|
|
|
var content: some HTML<HTMLTag.div> {
|
|
div {
|
|
span(.class("text-sm")) { label }
|
|
p(.class("text-2xl font-semibold")) {
|
|
"\(double: value, fractionDigits: fractionDigits)"
|
|
span(.class("text-base font-normal ps-2")) { units }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private struct PsychrometricPropertiesGrid: HTML, Sendable {
|
|
let title: String
|
|
let properties: PsychrometricProperties
|
|
|
|
var content: some HTML<HTMLTag.div> {
|
|
div(.class("rounded-xl border border-blue-600 dark:border-gray-400 bg-blue-100 dark:bg-slate-700 p-4")) {
|
|
h4(.class("""
|
|
text-lg font-semibold flex justify-center py-2 mb-4 text-blue-600 dark:text-slate-300
|
|
""")) { title }
|
|
PsychrometricPropertiesView(properties: properties)
|
|
.attributes(.class("grid grid-cols-1 gap-4"))
|
|
}
|
|
}
|
|
}
|
|
}
|