feat: Initial duct sizing view and calculations, need to add supply and return trunk sizing.

This commit is contained in:
2026-01-09 12:43:56 -05:00
parent 30fddb9dce
commit 7083178844
15 changed files with 402 additions and 15 deletions

View File

@@ -20,10 +20,6 @@ struct ComponentLossForm: HTML, Sendable {
for: .project(.detail(projectID, .componentLoss(.index)))
)
.appendingPath(componentLoss?.id)
// if let componentLoss {
// return baseRoute.appending("/\(componentLoss.id)")
// }
// return baseRoute
}
var body: some HTML {

View File

@@ -0,0 +1,81 @@
import Elementary
import ElementaryHTMX
import ManualDCore
import Styleguide
// TODO: Add error text if prior steps are not completed.
struct DuctSizingView: HTML, Sendable {
let rooms: [DuctSizing.RoomContainer]
var body: some HTML {
div {
h1(.class("text-2xl py-4")) { "Duct Sizes" }
div(.class("overflow-x-auto")) {
table(.class("table table-zebra")) {
thead {
tr(.class("text-xl text-gray-400 font-bold")) {
th { "ID" }
th { "Name" }
th { "H-BTU" }
th { "C-BTU" }
th(.class("hidden 2xl:table-cell")) { "Htg CFM" }
th(.class("hidden 2xl:table-cell")) { "Clg CFM" }
th { "Dsn CFM" }
th(.class("hidden xl:table-cell")) { "Round Size" }
th { "Velocity" }
th { "Final Size" }
th { "Flex Size" }
}
}
tbody {
for room in rooms {
RoomRow(room: room)
}
}
}
}
}
}
struct RoomRow: HTML, Sendable {
let room: DuctSizing.RoomContainer
var body: some HTML<HTMLTag.tr> {
tr(.class("text-lg")) {
td { room.registerID }
td { room.roomName }
td { Number(room.heatingLoad, digits: 0) }
td { Number(room.coolingLoad, digits: 0) }
td(.class("hidden 2xl:table-cell")) { Number(room.heatingCFM, digits: 0) }
td(.class("hidden 2xl:table-cell")) { Number(room.coolingCFM, digits: 0) }
td {
Number(room.designCFM.value, digits: 0)
.attributes(
.class("badge badge-outline badge-\(room.designCFM.color) text-xl font-bold"))
}
td(.class("hidden xl:table-cell")) { Number(room.roundSize, digits: 0) }
td { Number(room.velocity) }
td {
Number(room.finalSize)
.attributes(.class("badge badge-outline badge-secondary text-xl font-bold"))
}
td {
Number(room.flexSize)
.attributes(.class("badge badge-outline badge-primary text-xl font-bold"))
}
}
}
}
}
extension DuctSizing.DesignCFM {
var color: String {
switch self {
case .heating: return "error"
case .cooling: return "info"
}
}
}

View File

@@ -2,21 +2,27 @@ import DatabaseClient
import Dependencies
import Elementary
import ElementaryHTMX
import Logging
import ManualDClient
import ManualDCore
import Styleguide
struct ProjectView: HTML, Sendable {
@Dependency(\.database) var database
@Dependency(\.manualD) var manualD
let projectID: Project.ID
let activeTab: SiteRoute.View.ProjectRoute.DetailRoute.Tab
let logger: Logger?
init(
projectID: Project.ID,
activeTab: SiteRoute.View.ProjectRoute.DetailRoute.Tab
activeTab: SiteRoute.View.ProjectRoute.DetailRoute.Tab,
logger: Logger? = nil
) {
self.projectID = projectID
self.activeTab = activeTab
self.logger = logger
}
var body: some HTML {
@@ -61,7 +67,15 @@ struct ProjectView: HTML, Sendable {
projectID: projectID
)
case .ductSizing:
div { "FIX ME!" }
try await DuctSizingView(
rooms: manualD.calculate(
rooms: database.rooms.fetch(projectID),
designFrictionRateResult: database.designFrictionRate(projectID: projectID),
projectSHR: database.projects.getSensibleHeatRatio(projectID),
logger: logger
)
)
// div { "FIX ME!" }
}
}
@@ -178,7 +192,11 @@ extension ProjectView {
}
li(.class("w-full")) {
row(
title: "Duct Sizes", icon: .wind, href: "#", isComplete: false, hideIsComplete: true
title: "Duct Sizes",
icon: .wind,
route: .project(.detail(projectID, .ductSizing(.index))),
isComplete: false,
hideIsComplete: true
)
.attributes(.class("btn-active"), when: active == .ductSizing)
}

View File

@@ -8,20 +8,21 @@ import Styleguide
// TODO: Need to hold the project ID in hidden input field.
struct RoomForm: HTML, Sendable {
static let id = "roomForm"
static func id(_ room: Room? = nil) -> String {
let baseId = "roomForm"
guard let room else { return baseId }
return baseId.appending("_\(room.id.idString)")
}
let id: String
let dismiss: Bool
let projectID: Project.ID
let room: Room?
init(
id: String = Self.id,
dismiss: Bool,
projectID: Project.ID,
room: Room? = nil
) {
self.id = id
self.dismiss = dismiss
self.projectID = projectID
self.room = room
@@ -35,7 +36,7 @@ struct RoomForm: HTML, Sendable {
}
var body: some HTML {
ModalForm(id: id, dismiss: dismiss) {
ModalForm(id: Self.id(room), dismiss: dismiss) {
h1(.class("text-3xl font-bold pb-6")) { "Room" }
form(
.class("modal-backdrop"),

View File

@@ -22,7 +22,7 @@ struct RoomsView: HTML, Sendable {
) {
div(.class("flex me-4")) {
PlusButton()
.attributes(.showModal(id: RoomForm.id))
.attributes(.showModal(id: RoomForm.id()))
}
}
}
@@ -134,12 +134,11 @@ struct RoomsView: HTML, Sendable {
EditButton()
.attributes(
.class("join-item"),
.showModal(id: "roomForm_\(room.name)")
.showModal(id: RoomForm.id(room))
)
}
}
RoomForm(
id: "roomForm_\(room.name)",
dismiss: true,
projectID: room.projectID,
room: room